igroffman commited on
Commit
ce23775
·
verified ·
1 Parent(s): 5d5706f

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +238 -52
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
- fluidRow(
5787
- column(
5788
- 4,
5789
- div(class = "main-panel",
5790
- div(class = "upload-box",
5791
- h3("Upload Game Data", style = "color: #006F71; margin-top: 0;"),
5792
- radioButtons("report_type", "Report Type",
5793
- c("Hitter Report"="hitter",
5794
- "Camp Pitcher Report"="pitcher",
5795
- "Isaac Pitcher Report"="advanced_pitcher",
5796
- "Matt Williams Report"="tableau_pitcher",
5797
- "Catcher Report"="catcher",
5798
- "Umpire Report"="umpire",
5799
- "BP Report"="bp",
5800
- "Bullpen Report (Portable) "="bullpen"),
5801
- selected = "hitter", inline = TRUE),
 
 
 
 
5802
 
5803
- conditionalPanel(
5804
- condition = "input.report_type != 'bullpen'",
5805
- fileInput("game_csv", NULL, accept = c(".csv","text/csv"),
5806
- buttonLabel = "Choose Game CSV...", placeholder = "No file selected")
5807
- ),
5808
 
5809
- conditionalPanel(
5810
- condition = "input.report_type == 'bullpen'",
5811
- fileInput("bullpen_csv", "Bullpen CSV", accept = c(".csv","text/csv"),
5812
- buttonLabel = "Choose Bullpen CSV...", placeholder = "Upload bullpen pitching data"),
5813
- p("Upload a bullpen-session TrackMan CSV (no game data needed)",
5814
- style = "font-size: 0.85em; color: #666; margin-top: -10px;")
5815
- ),
5816
 
5817
- hr(),
5818
- h4("Optional: Bio CSV", style = "color: #006F71; font-size: 1em;"),
5819
- conditionalPanel("input.report_type == 'hitter'",
5820
- fileInput("bio_csv_hitter", "Player Bio (optional)", accept = c(".csv","text/csv"),
5821
- buttonLabel = "Choose Bio CSV...", placeholder = "Optional"),
5822
- p("Upload CCU_Hitter_Bio.csv to add headshots",
5823
- style = "font-size: 0.85em; color: #666; margin-top: -10px;")
 
 
 
 
 
5824
  ),
5825
- conditionalPanel("input.report_type == 'catcher'",
5826
- fileInput("bio_csv_catcher", "Catcher Bio (optional)", accept = c(".csv","text/csv"),
5827
- buttonLabel = "Choose Bio CSV...", placeholder = "Optional")
5828
- )
5829
- ),
5830
- uiOutput("selector_ui"),
5831
- hr(),
5832
- uiOutput("download_ui"),
5833
- uiOutput("bulk_ui"),
5834
- uiOutput("status_message")
 
 
 
 
5835
  )
5836
  ),
5837
- column(
5838
- 8,
5839
- div(class = "main-panel",
5840
- h3("Report Preview", style = "color: #006F71; margin-top: 0;"),
5841
- uiOutput("preview_content")
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)