Spaces:
Running
Running
Update app.R
Browse files
app.R
CHANGED
|
@@ -163,7 +163,6 @@ app_css <- "
|
|
| 163 |
}
|
| 164 |
"
|
| 165 |
|
| 166 |
-
|
| 167 |
stuffplus_recipe <- readRDS("stuffplus_recipe.rds")
|
| 168 |
|
| 169 |
stuffplus_model <- xgb.load("stuffplus_xgb.json")
|
|
@@ -5783,63 +5782,128 @@ app_ui <- fluidPage(
|
|
| 5783 |
p("Upload a Trackman CSV to generate postgame reports | To generate a bullpen report first select the bullpen option then upload a portable trackman CSV")
|
| 5784 |
),
|
| 5785 |
uiOutput("leaderboard_ui"),
|
| 5786 |
-
|
| 5787 |
-
|
| 5788 |
-
|
| 5789 |
-
|
| 5790 |
-
|
| 5791 |
-
|
| 5792 |
-
|
| 5793 |
-
|
| 5794 |
-
|
| 5795 |
-
|
| 5796 |
-
|
| 5797 |
-
|
| 5798 |
-
|
| 5799 |
-
|
| 5800 |
-
|
| 5801 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5802 |
|
| 5803 |
-
|
| 5804 |
-
|
| 5805 |
-
|
| 5806 |
-
|
| 5807 |
-
|
| 5808 |
|
| 5809 |
-
|
| 5810 |
-
|
| 5811 |
-
|
| 5812 |
-
|
| 5813 |
-
|
| 5814 |
-
|
| 5815 |
-
|
| 5816 |
|
| 5817 |
-
|
| 5818 |
-
|
| 5819 |
-
|
| 5820 |
-
|
| 5821 |
-
|
| 5822 |
-
|
| 5823 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5824 |
),
|
| 5825 |
-
|
| 5826 |
-
|
| 5827 |
-
|
| 5828 |
-
)
|
| 5829 |
-
|
| 5830 |
-
|
| 5831 |
-
|
| 5832 |
-
|
| 5833 |
-
|
| 5834 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5835 |
)
|
| 5836 |
),
|
| 5837 |
-
|
| 5838 |
-
|
| 5839 |
-
|
| 5840 |
-
|
| 5841 |
-
|
| 5842 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5843 |
)
|
| 5844 |
)
|
| 5845 |
)
|
|
@@ -6883,6 +6947,128 @@ server <- function(input, output, session) {
|
|
| 6883 |
},
|
| 6884 |
contentType = "application/zip"
|
| 6885 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6886 |
}
|
| 6887 |
|
| 6888 |
shinyApp(ui = ui, server = server)
|
|
|
|
| 163 |
}
|
| 164 |
"
|
| 165 |
|
|
|
|
| 166 |
stuffplus_recipe <- readRDS("stuffplus_recipe.rds")
|
| 167 |
|
| 168 |
stuffplus_model <- xgb.load("stuffplus_xgb.json")
|
|
|
|
| 5782 |
p("Upload a Trackman CSV to generate postgame reports | To generate a bullpen report first select the bullpen option then upload a portable trackman CSV")
|
| 5783 |
),
|
| 5784 |
uiOutput("leaderboard_ui"),
|
| 5785 |
+
tabsetPanel(
|
| 5786 |
+
id = "main_tabs",
|
| 5787 |
+
tabPanel("Generate Reports",
|
| 5788 |
+
br(),
|
| 5789 |
+
fluidRow(
|
| 5790 |
+
column(
|
| 5791 |
+
4,
|
| 5792 |
+
div(class = "main-panel",
|
| 5793 |
+
div(class = "upload-box",
|
| 5794 |
+
h3("Upload Game Data", style = "color: #006F71; margin-top: 0;"),
|
| 5795 |
+
radioButtons("report_type", "Report Type",
|
| 5796 |
+
c("Hitter Report"="hitter",
|
| 5797 |
+
"Camp Pitcher Report"="pitcher",
|
| 5798 |
+
"Isaac Pitcher Report"="advanced_pitcher",
|
| 5799 |
+
"Matt Williams Report"="tableau_pitcher",
|
| 5800 |
+
"Catcher Report"="catcher",
|
| 5801 |
+
"Umpire Report"="umpire",
|
| 5802 |
+
"BP Report"="bp",
|
| 5803 |
+
"Bullpen Report (Portable) "="bullpen"),
|
| 5804 |
+
selected = "hitter", inline = TRUE),
|
| 5805 |
|
| 5806 |
+
conditionalPanel(
|
| 5807 |
+
condition = "input.report_type != 'bullpen'",
|
| 5808 |
+
fileInput("game_csv", NULL, accept = c(".csv","text/csv"),
|
| 5809 |
+
buttonLabel = "Choose Game CSV...", placeholder = "No file selected")
|
| 5810 |
+
),
|
| 5811 |
|
| 5812 |
+
conditionalPanel(
|
| 5813 |
+
condition = "input.report_type == 'bullpen'",
|
| 5814 |
+
fileInput("bullpen_csv", "Bullpen CSV", accept = c(".csv","text/csv"),
|
| 5815 |
+
buttonLabel = "Choose Bullpen CSV...", placeholder = "Upload bullpen pitching data"),
|
| 5816 |
+
p("Upload a bullpen-session TrackMan CSV (no game data needed)",
|
| 5817 |
+
style = "font-size: 0.85em; color: #666; margin-top: -10px;")
|
| 5818 |
+
),
|
| 5819 |
|
| 5820 |
+
hr(),
|
| 5821 |
+
h4("Optional: Bio CSV", style = "color: #006F71; font-size: 1em;"),
|
| 5822 |
+
conditionalPanel("input.report_type == 'hitter'",
|
| 5823 |
+
fileInput("bio_csv_hitter", "Player Bio (optional)", accept = c(".csv","text/csv"),
|
| 5824 |
+
buttonLabel = "Choose Bio CSV...", placeholder = "Optional"),
|
| 5825 |
+
p("Upload CCU_Hitter_Bio.csv to add headshots",
|
| 5826 |
+
style = "font-size: 0.85em; color: #666; margin-top: -10px;")
|
| 5827 |
+
),
|
| 5828 |
+
conditionalPanel("input.report_type == 'catcher'",
|
| 5829 |
+
fileInput("bio_csv_catcher", "Catcher Bio (optional)", accept = c(".csv","text/csv"),
|
| 5830 |
+
buttonLabel = "Choose Bio CSV...", placeholder = "Optional")
|
| 5831 |
+
)
|
| 5832 |
),
|
| 5833 |
+
uiOutput("selector_ui"),
|
| 5834 |
+
hr(),
|
| 5835 |
+
uiOutput("download_ui"),
|
| 5836 |
+
uiOutput("bulk_ui"),
|
| 5837 |
+
uiOutput("status_message")
|
| 5838 |
+
)
|
| 5839 |
+
),
|
| 5840 |
+
column(
|
| 5841 |
+
8,
|
| 5842 |
+
div(class = "main-panel",
|
| 5843 |
+
h3("Report Preview", style = "color: #006F71; margin-top: 0;"),
|
| 5844 |
+
uiOutput("preview_content")
|
| 5845 |
+
)
|
| 5846 |
+
)
|
| 5847 |
)
|
| 5848 |
),
|
| 5849 |
+
tabPanel("Data Processing",
|
| 5850 |
+
br(),
|
| 5851 |
+
fluidRow(
|
| 5852 |
+
column(
|
| 5853 |
+
4,
|
| 5854 |
+
div(class = "main-panel",
|
| 5855 |
+
div(class = "upload-box",
|
| 5856 |
+
h3("Process Raw TrackMan Data", style = "color: #006F71; margin-top: 0;"),
|
| 5857 |
+
p("Clean a raw TrackMan export (add result metrics, run values) and optionally append Stuff+, then download the processed dataset for use in the report tabs.",
|
| 5858 |
+
style = "font-size: 0.85em; color: #666;"),
|
| 5859 |
+
fileInput("proc_file", "Game Data (CSV or Parquet)",
|
| 5860 |
+
accept = c(".csv", "text/csv", ".parquet"),
|
| 5861 |
+
buttonLabel = "Choose File...", placeholder = "No file selected"),
|
| 5862 |
+
fileInput("proc_json", "Bat Tracking JSON (optional)",
|
| 5863 |
+
accept = c(".json", "application/json"),
|
| 5864 |
+
buttonLabel = "Choose JSON...", placeholder = "Optional - merged by PitchUID"),
|
| 5865 |
+
radioButtons("proc_date_format", "Date Format",
|
| 5866 |
+
c("YYYY-MM-DD" = "yyyy", "M/D/YY" = "mdyy"),
|
| 5867 |
+
selected = "yyyy", inline = TRUE),
|
| 5868 |
+
checkboxInput("proc_run_stuff", "Append Stuff+ predictions", value = TRUE),
|
| 5869 |
+
actionButton("proc_run", "Process Data", class = "btn-primary",
|
| 5870 |
+
style = "margin-top: 10px;")
|
| 5871 |
+
),
|
| 5872 |
+
br(),
|
| 5873 |
+
div(class = "upload-box",
|
| 5874 |
+
h4("Columns to Remove", style = "color: #006F71; margin-top: 0;"),
|
| 5875 |
+
div(style = "margin-bottom: 8px;",
|
| 5876 |
+
actionButton("proc_cols_all", "Select All", class = "btn-secondary",
|
| 5877 |
+
style = "width:auto; padding:4px 10px; font-size:12px; margin-right:4px;"),
|
| 5878 |
+
actionButton("proc_cols_none", "Clear", class = "btn-secondary",
|
| 5879 |
+
style = "width:auto; padding:4px 10px; font-size:12px; margin-right:4px;"),
|
| 5880 |
+
actionButton("proc_cols_spin", "Spin-Axis Only", class = "btn-secondary",
|
| 5881 |
+
style = "width:auto; padding:4px 10px; font-size:12px;")
|
| 5882 |
+
),
|
| 5883 |
+
div(style = "max-height: 220px; overflow-y: auto; font-size: 0.85em;",
|
| 5884 |
+
checkboxGroupInput("proc_drop_cols", NULL,
|
| 5885 |
+
choices = columns_to_remove,
|
| 5886 |
+
selected = columns_to_remove)
|
| 5887 |
+
)
|
| 5888 |
+
),
|
| 5889 |
+
hr(),
|
| 5890 |
+
uiOutput("proc_download_ui")
|
| 5891 |
+
)
|
| 5892 |
+
),
|
| 5893 |
+
column(
|
| 5894 |
+
8,
|
| 5895 |
+
div(class = "main-panel",
|
| 5896 |
+
h3("Processed Data", style = "color: #006F71; margin-top: 0;"),
|
| 5897 |
+
uiOutput("proc_status"),
|
| 5898 |
+
verbatimTextOutput("proc_summary"),
|
| 5899 |
+
br(),
|
| 5900 |
+
h4("Preview (first 15 rows)", style = "color: #006F71;"),
|
| 5901 |
+
div(style = "overflow-x: auto;",
|
| 5902 |
+
tableOutput("proc_preview")
|
| 5903 |
+
)
|
| 5904 |
+
)
|
| 5905 |
+
)
|
| 5906 |
+
)
|
| 5907 |
)
|
| 5908 |
)
|
| 5909 |
)
|
|
|
|
| 6947 |
},
|
| 6948 |
contentType = "application/zip"
|
| 6949 |
)
|
| 6950 |
+
|
| 6951 |
+
# ===================== DATA PROCESSING TAB =====================
|
| 6952 |
+
proc_result <- reactiveVal(NULL)
|
| 6953 |
+
|
| 6954 |
+
# Column-selection helper buttons
|
| 6955 |
+
observeEvent(input$proc_cols_all, {
|
| 6956 |
+
updateCheckboxGroupInput(session, "proc_drop_cols", selected = columns_to_remove)
|
| 6957 |
+
})
|
| 6958 |
+
observeEvent(input$proc_cols_none, {
|
| 6959 |
+
updateCheckboxGroupInput(session, "proc_drop_cols", selected = character(0))
|
| 6960 |
+
})
|
| 6961 |
+
observeEvent(input$proc_cols_spin, {
|
| 6962 |
+
spin_cols <- columns_to_remove[grepl("SpinAxis3d", columns_to_remove)]
|
| 6963 |
+
updateCheckboxGroupInput(session, "proc_drop_cols", selected = spin_cols)
|
| 6964 |
+
})
|
| 6965 |
+
|
| 6966 |
+
# Run the pipeline
|
| 6967 |
+
observeEvent(input$proc_run, {
|
| 6968 |
+
if (is.null(input$proc_file)) {
|
| 6969 |
+
showNotification("Please choose a CSV or Parquet file first.", type = "warning", duration = 5)
|
| 6970 |
+
return(NULL)
|
| 6971 |
+
}
|
| 6972 |
+
json_path <- if (!is.null(input$proc_json)) input$proc_json$datapath else NULL
|
| 6973 |
+
|
| 6974 |
+
withProgress(message = "Processing data...", value = 0, {
|
| 6975 |
+
result <- tryCatch({
|
| 6976 |
+
incProgress(0.2, detail = "Reading and cleaning")
|
| 6977 |
+
df <- process_uploaded_trackman(
|
| 6978 |
+
filepath = input$proc_file$datapath,
|
| 6979 |
+
filename = input$proc_file$name,
|
| 6980 |
+
json_path = json_path,
|
| 6981 |
+
drop_columns = input$proc_drop_cols %||% character(0),
|
| 6982 |
+
date_format = input$proc_date_format,
|
| 6983 |
+
run_stuffplus = isTRUE(input$proc_run_stuff)
|
| 6984 |
+
)
|
| 6985 |
+
incProgress(0.7, detail = "Finalizing")
|
| 6986 |
+
df
|
| 6987 |
+
}, error = function(e) {
|
| 6988 |
+
showNotification(paste("Processing error:", e$message), type = "error", duration = 10)
|
| 6989 |
+
NULL
|
| 6990 |
+
})
|
| 6991 |
+
proc_result(result)
|
| 6992 |
+
})
|
| 6993 |
+
|
| 6994 |
+
if (!is.null(proc_result())) {
|
| 6995 |
+
showNotification(
|
| 6996 |
+
paste0("Processed ", format(nrow(proc_result()), big.mark = ","),
|
| 6997 |
+
" rows x ", ncol(proc_result()), " columns."),
|
| 6998 |
+
type = "message", duration = 4
|
| 6999 |
+
)
|
| 7000 |
+
}
|
| 7001 |
+
})
|
| 7002 |
+
|
| 7003 |
+
output$proc_status <- renderUI({
|
| 7004 |
+
if (is.null(proc_result())) {
|
| 7005 |
+
div(style = "color:#888; font-style:italic;",
|
| 7006 |
+
"Upload a raw TrackMan file and click Process Data to begin.")
|
| 7007 |
+
} else {
|
| 7008 |
+
div(style = "color:#006F71; font-weight:bold;", "Processing complete.")
|
| 7009 |
+
}
|
| 7010 |
+
})
|
| 7011 |
+
|
| 7012 |
+
output$proc_summary <- renderPrint({
|
| 7013 |
+
df <- proc_result()
|
| 7014 |
+
if (is.null(df)) return(invisible(NULL))
|
| 7015 |
+
has_stuff <- "stuff_plus" %in% names(df)
|
| 7016 |
+
has_bat <- "BatSpeed_Sensor" %in% names(df)
|
| 7017 |
+
cat("Rows: ", format(nrow(df), big.mark = ","), "\n", sep = "")
|
| 7018 |
+
cat("Columns: ", ncol(df), "\n", sep = "")
|
| 7019 |
+
cat("Stuff+ added:", if (has_stuff) " yes" else " no", "\n", sep = "")
|
| 7020 |
+
if (has_stuff) {
|
| 7021 |
+
sp <- suppressWarnings(round(mean(df$stuff_plus, na.rm = TRUE), 1))
|
| 7022 |
+
cat("Mean Stuff+: ", sp, "\n", sep = "")
|
| 7023 |
+
}
|
| 7024 |
+
if (has_bat) {
|
| 7025 |
+
matched <- sum(!is.na(df$BatSpeed_Sensor))
|
| 7026 |
+
cat("Bat-tracking matched:", matched, "\n", sep = " ")
|
| 7027 |
+
}
|
| 7028 |
+
})
|
| 7029 |
+
|
| 7030 |
+
output$proc_preview <- renderTable({
|
| 7031 |
+
df <- proc_result()
|
| 7032 |
+
if (is.null(df)) return(NULL)
|
| 7033 |
+
pref <- c("Date", "Pitcher", "Batter", "TaggedPitchType", "PitchCall",
|
| 7034 |
+
"PlayResult", "RelSpeed", "SpinRate", "stuff_plus", "woba")
|
| 7035 |
+
cols <- intersect(pref, names(df))
|
| 7036 |
+
if (length(cols) == 0) cols <- names(df)[seq_len(min(10, ncol(df)))]
|
| 7037 |
+
head(df[, cols, drop = FALSE], 15)
|
| 7038 |
+
}, striped = TRUE, hover = TRUE, bordered = TRUE, spacing = "xs", digits = 2, na = "")
|
| 7039 |
+
|
| 7040 |
+
output$proc_download_ui <- renderUI({
|
| 7041 |
+
if (is.null(proc_result())) return(NULL)
|
| 7042 |
+
tagList(
|
| 7043 |
+
downloadButton("proc_download_csv", "Download Processed CSV", class = "btn-primary"),
|
| 7044 |
+
br(), br(),
|
| 7045 |
+
downloadButton("proc_download_parquet", "Download Processed Parquet", class = "btn-secondary")
|
| 7046 |
+
)
|
| 7047 |
+
})
|
| 7048 |
+
|
| 7049 |
+
output$proc_download_csv <- downloadHandler(
|
| 7050 |
+
filename = function() {
|
| 7051 |
+
base <- if (!is.null(input$proc_file)) tools::file_path_sans_ext(input$proc_file$name) else "trackman"
|
| 7052 |
+
paste0(base, "_processed.csv")
|
| 7053 |
+
},
|
| 7054 |
+
content = function(file) {
|
| 7055 |
+
df <- proc_result(); req(df)
|
| 7056 |
+
write.csv(df, file, row.names = FALSE, na = "")
|
| 7057 |
+
}
|
| 7058 |
+
)
|
| 7059 |
+
|
| 7060 |
+
output$proc_download_parquet <- downloadHandler(
|
| 7061 |
+
filename = function() {
|
| 7062 |
+
base <- if (!is.null(input$proc_file)) tools::file_path_sans_ext(input$proc_file$name) else "trackman"
|
| 7063 |
+
paste0(base, "_processed.parquet")
|
| 7064 |
+
},
|
| 7065 |
+
content = function(file) {
|
| 7066 |
+
df <- proc_result(); req(df)
|
| 7067 |
+
arrow::write_parquet(df, file)
|
| 7068 |
+
}
|
| 7069 |
+
)
|
| 7070 |
+
# =================== END DATA PROCESSING TAB ===================
|
| 7071 |
+
|
| 7072 |
}
|
| 7073 |
|
| 7074 |
shinyApp(ui = ui, server = server)
|