blob: 005078fdf087ffae3e9ce8f2141becd01db68674 [file] [log] [blame]
Philipp Schrader46304f62023-03-05 14:11:19 -08001package driver_ranking
2
3import (
4 "encoding/csv"
5 "errors"
6 "fmt"
7 "log"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strconv"
12
13 "github.com/bazelbuild/rules_go/go/runfiles"
14 "github.com/frc971/971-Robot-Code/scouting/db"
15)
16
17const (
18 DEFAULT_SCRIPT_PATH = "org_frc971/scouting/DriverRank/src/DriverRank.jl"
19)
20
21type Database interface {
22 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
23 AddParsedDriverRanking(data db.ParsedDriverRankingData) error
24}
25
26func writeToCsv(filename string, records [][]string) error {
27 file, err := os.Create(filename)
28 if err != nil {
29 return errors.New(fmt.Sprintf("Failed to create %s: %v", filename, err))
30 }
31 defer file.Close()
32
33 writer := csv.NewWriter(file)
34 writer.WriteAll(records)
35 if err := writer.Error(); err != nil {
36 return errors.New(fmt.Sprintf("Failed to write to %s: %v", filename, err))
37 }
38
39 return nil
40}
41
42func readFromCsv(filename string) (records [][]string, err error) {
43 file, err := os.Open(filename)
44 if err != nil {
45 return nil, errors.New(fmt.Sprintf("Failed to open %s: %v", filename, err))
46 }
47 defer file.Close()
48
49 reader := csv.NewReader(file)
50 records, err = reader.ReadAll()
51 if err != nil {
52 return nil, errors.New(fmt.Sprintf("Failed to parse %s as CSV: %v", filename, err))
53 }
54
55 return
56}
57
58// Runs the specified script on the DriverRankingData that the scouts collected
59// and dumps the results in the ParsedDriverRankingData table. If the script is
60// not specified (i.e. empty string) then the
61// scouting/DriverRank/src/DriverRank.jl script is called instead.
62func GenerateFullDriverRanking(database Database, scriptPath string) {
63 rawRankings, err := database.ReturnAllDriverRankings()
64 if err != nil {
65 log.Println("Failed to get raw driver ranking data: ", err)
66 return
67 }
68
69 records := [][]string{
70 {"Timestamp", "Scout Name", "Match Number", "Alliance", "Rank 1 (best)", "Rank 2", "Rank 3 (worst)"},
71 }
72
73 // Populate the CSV data.
74 for _, ranking := range rawRankings {
75 records = append(records, []string{
76 // Most of the data is unused so we just fill in empty
77 // strings.
78 "", "", "", "",
79 strconv.Itoa(int(ranking.Rank1)),
80 strconv.Itoa(int(ranking.Rank2)),
81 strconv.Itoa(int(ranking.Rank3)),
82 })
83 }
84
85 dir, err := os.MkdirTemp("", "driver_ranking_eval")
86 if err != nil {
87 log.Println("Failed to create temporary driver_ranking_eval dir: ", err)
88 return
89 }
90 defer os.RemoveAll(dir)
91
92 inputCsvFile := filepath.Join(dir, "input.csv")
93 outputCsvFile := filepath.Join(dir, "output.csv")
94
95 if err := writeToCsv(inputCsvFile, records); err != nil {
96 log.Println("Failed to write input CSV: ", err)
97 return
98 }
99
100 // If the user didn't specify a script, use the default one.
101 if scriptPath == "" {
102 scriptPath, err = runfiles.Rlocation(DEFAULT_SCRIPT_PATH)
103 if err != nil {
104 log.Println("Failed to find runfiles entry for ", DEFAULT_SCRIPT_PATH, ": ", err)
105 return
106 }
107 }
108
109 // Run the analysis script.
110 cmd := exec.Command(scriptPath, inputCsvFile, outputCsvFile)
111 cmd.Stdout = os.Stdout
112 cmd.Stderr = os.Stderr
113 if err := cmd.Run(); err != nil {
114 log.Println("Failed to run the driver ranking script: ", err)
115 return
116 }
117
118 // Grab the output from the analysis script and insert it into the
119 // database.
120 outputRecords, err := readFromCsv(outputCsvFile)
121
122 for _, record := range outputRecords {
123 score, err := strconv.ParseFloat(record[1], 32)
124 if err != nil {
125 log.Println("Failed to parse score for team ", record[0], ": ", record[1], ": ", err)
126 return
127 }
128
129 err = database.AddParsedDriverRanking(db.ParsedDriverRankingData{
130 TeamNumber: record[0],
131 Score: float32(score),
132 })
133 if err != nil {
134 log.Println("Failed to insert driver ranking score for team ", record[0], ": ", err)
135 return
136 }
137 }
138}