blob: 75309d9cbd1a94c58ccafe5ddc250df6aacdc1b6 [file] [log] [blame]
Sabina Leaverc5fd2772022-01-29 17:00:23 -08001package db
2
3import (
Philipp Schrader30005e42022-03-06 13:53:58 -08004 "errors"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08005 "fmt"
Philipp Schradereecb8962022-06-01 21:02:42 -07006 "gorm.io/driver/postgres"
7 "gorm.io/gorm"
8 "gorm.io/gorm/clause"
9 "gorm.io/gorm/logger"
Sabina Leaverc5fd2772022-01-29 17:00:23 -080010)
11
12type Database struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070013 *gorm.DB
Sabina Leaverc5fd2772022-01-29 17:00:23 -080014}
15
Emily Markovabf24c9e2023-02-08 20:31:11 -080016type TeamMatch struct {
17 MatchNumber int32 `gorm:"primaryKey"`
18 SetNumber int32 `gorm:"primaryKey"`
19 CompLevel string `gorm:"primaryKey"`
20 Alliance string `gorm:"primaryKey"` // "R" or "B"
21 AlliancePosition int32 `gorm:"primaryKey"` // 1, 2, or 3
22 TeamNumber int32
Sabina Leaverc5fd2772022-01-29 17:00:23 -080023}
24
Milo Lina72e2002022-04-06 20:31:13 -070025type Shift struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070026 MatchNumber int32 `gorm:"primaryKey"`
Milo Lina72e2002022-04-06 20:31:13 -070027 R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter string
28}
29
Sabina Leaverc5fd2772022-01-29 17:00:23 -080030type Stats struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070031 TeamNumber int32 `gorm:"primaryKey"`
32 MatchNumber int32 `gorm:"primaryKey"`
33 SetNumber int32 `gorm:"primaryKey"`
34 CompLevel string `gorm:"primaryKey"`
35 StartingQuadrant int32
36 // This field is for the balls picked up during auto. Use this field
37 // when using this library. Ignore the AutoBallPickedUpX fields below.
38 AutoBallPickedUp [5]bool `gorm:"-:all"`
39 // These fields are internal implementation details. Do not use these.
40 // TODO(phil): Figure out how to use the JSON gorm serializer instead
41 // of manually serializing/deserializing these.
42 AutoBallPickedUp1 bool
43 AutoBallPickedUp2 bool
44 AutoBallPickedUp3 bool
45 AutoBallPickedUp4 bool
46 AutoBallPickedUp5 bool
Philipp Schraderfee07e12022-03-17 22:19:47 -070047 // TODO(phil): Re-order auto and teleop fields so auto comes first.
Philipp Schraderfa45d742022-03-18 19:29:05 -070048 ShotsMissed, UpperGoalShots, LowerGoalShots int32
49 ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto int32
50 PlayedDefense, DefenseReceivedScore int32
Philipp Schrader36df73a2022-03-17 23:27:24 -070051 // Climbing level:
52 // 0 -> "NoAttempt"
53 // 1 -> "Failed"
54 // 2 -> "FailedWithPlentyOfTime"
55 // 3 -> "Low"
56 // 4 -> "Medium"
57 // 5 -> "High"
Philipp Schrader85f6e5b2022-04-16 14:20:06 -070058 // 6 -> "Traversal"
Philipp Schrader36df73a2022-03-17 23:27:24 -070059 Climbing int32
Philipp Schraderfa45d742022-03-18 19:29:05 -070060 // Some non-numerical data that the scout felt worth noting.
61 Comment string
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070062 // The username of the person who collected these statistics.
63 // "unknown" if submitted without logging in.
64 // Empty if the stats have not yet been collected.
65 CollectedBy string
Sabina Leaverc5fd2772022-01-29 17:00:23 -080066}
67
Sabina Leaver759090b2023-01-14 20:42:56 -080068type Stats2023 struct {
69 TeamNumber string `gorm:"primaryKey"`
70 MatchNumber int32 `gorm:"primaryKey"`
71 SetNumber int32 `gorm:"primaryKey"`
72 CompLevel string `gorm:"primaryKey"`
73 StartingQuadrant int32
74 LowCubesAuto, MiddleCubesAuto, HighCubesAuto, CubesDroppedAuto int32
75 LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
76 LowCubes, MiddleCubes, HighCubes, CubesDropped int32
77 LowCones, MiddleCones, HighCones, ConesDropped int32
78 AvgCycle int32
79 // The username of the person who collected these statistics.
80 // "unknown" if submitted without logging in.
81 // Empty if the stats have not yet been collected.
82 CollectedBy string
83}
84
85type Action struct {
86 TeamNumber string `gorm:"primaryKey"`
87 MatchNumber int32 `gorm:"primaryKey"`
88 SetNumber int32 `gorm:"primaryKey"`
89 CompLevel string `gorm:"primaryKey"`
90 CompletedAction []byte
91 // This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
92 TimeStamp int32 `gorm:"primaryKey"`
93 CollectedBy string
94}
95
Alex Perry871eab92022-03-12 17:43:52 -080096type NotesData struct {
Filip Kujawa7ddd5652023-03-07 19:56:15 -080097 ID uint `gorm:"primaryKey"`
98 TeamNumber int32
99 Notes string
100 GoodDriving bool
101 BadDriving bool
102 SketchyPickup bool
103 SketchyPlacing bool
104 GoodDefense bool
105 BadDefense bool
106 EasilyDefended bool
Alex Perry871eab92022-03-12 17:43:52 -0800107}
108
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700109type Ranking struct {
Philipp Schradereecb8962022-06-01 21:02:42 -0700110 TeamNumber int `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700111 Losses, Wins, Ties int32
112 Rank, Dq int32
113}
114
Filip Kujawa210a03b2022-11-24 14:41:11 -0800115type DriverRankingData struct {
116 // Each entry in the table is a single scout's ranking.
117 // Multiple scouts can submit a driver ranking for the same
118 // teams in the same match.
119 // The teams being ranked are stored in Rank1, Rank2, Rank3,
120 // Rank1 being the best driving and Rank3 being the worst driving.
121
122 ID uint `gorm:"primaryKey"`
123 MatchNumber int32
124 Rank1 int32
125 Rank2 int32
126 Rank3 int32
127}
128
Philipp Schradera8955fb2023-03-05 15:47:19 -0800129type ParsedDriverRankingData struct {
130 // This data stores the output of DriverRank.jl.
131
132 TeamNumber string `gorm:"primaryKey"`
133
134 // The score of the team. A difference of 100 in two team's scores
135 // indicates that one team will outperform the other in 90% of the
136 // matches.
137 Score float32
138}
139
Philipp Schrader7365d322022-03-06 16:40:08 -0800140// Opens a database at the specified port on localhost. We currently don't
141// support connecting to databases on other hosts.
142func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800143 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800144 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800145
Philipp Schradereecb8962022-06-01 21:02:42 -0700146 dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
147 database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
148 Logger: logger.Default.LogMode(logger.Silent),
149 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800150 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700151 database.Delete()
Philipp Schrader7365d322022-03-06 16:40:08 -0800152 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
153 }
Philipp Schrader36df73a2022-03-17 23:27:24 -0700154
Philipp Schradera8955fb2023-03-05 15:47:19 -0800155 err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats{}, &Stats2023{}, &Action{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
Philipp Schrader83fc2722022-03-10 21:59:20 -0800156 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700157 database.Delete()
158 return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700159 }
160
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800161 return database, nil
162}
163
164func (database *Database) Delete() error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700165 sql, err := database.DB.DB()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800166 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700167 return err
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800168 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700169 return sql.Close()
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800170}
171
Philipp Schradereecb8962022-06-01 21:02:42 -0700172func (database *Database) SetDebugLogLevel() {
173 database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
174}
Philipp Schradercd12c952022-04-08 18:58:49 -0700175
Emily Markovabf24c9e2023-02-08 20:31:11 -0800176func (database *Database) AddToMatch(m TeamMatch) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700177 result := database.Clauses(clause.OnConflict{
178 UpdateAll: true,
179 }).Create(&m)
180 return result.Error
Philipp Schradercd12c952022-04-08 18:58:49 -0700181}
182
Milo Lina72e2002022-04-06 20:31:13 -0700183func (database *Database) AddToShift(sh Shift) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700184 result := database.Clauses(clause.OnConflict{
185 UpdateAll: true,
186 }).Create(&sh)
187 return result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700188}
189
Sabina Leaver759090b2023-01-14 20:42:56 -0800190func (database *Database) AddAction(a Action) error {
191 result := database.Clauses(clause.OnConflict{
192 UpdateAll: true,
193 }).Create(&a)
194 return result.Error
195}
196
Philipp Schradercd12c952022-04-08 18:58:49 -0700197func (database *Database) AddToStats(s Stats) error {
Emily Markovaf5e63542023-01-22 16:22:03 -0800198 matches, err := database.queryMatches(s.TeamNumber)
Philipp Schradercd12c952022-04-08 18:58:49 -0700199 if err != nil {
200 return err
201 }
202 foundMatch := false
203 for _, match := range matches {
204 if match.MatchNumber == s.MatchNumber {
205 foundMatch = true
206 break
207 }
208 }
209 if !foundMatch {
210 return errors.New(fmt.Sprint(
211 "Failed to find team ", s.TeamNumber,
212 " in match ", s.MatchNumber, " in the schedule."))
213 }
214
Philipp Schradereecb8962022-06-01 21:02:42 -0700215 // Unpack the auto balls array.
216 s.AutoBallPickedUp1 = s.AutoBallPickedUp[0]
217 s.AutoBallPickedUp2 = s.AutoBallPickedUp[1]
218 s.AutoBallPickedUp3 = s.AutoBallPickedUp[2]
219 s.AutoBallPickedUp4 = s.AutoBallPickedUp[3]
220 s.AutoBallPickedUp5 = s.AutoBallPickedUp[4]
221 result := database.Create(&s)
222 return result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800223}
224
Sabina Leaver759090b2023-01-14 20:42:56 -0800225func (database *Database) AddToStats2023(s Stats2023) error {
226 matches, err := database.QueryMatchesString(s.TeamNumber)
227 if err != nil {
228 return err
229 }
230 foundMatch := false
231 for _, match := range matches {
232 if match.MatchNumber == s.MatchNumber {
233 foundMatch = true
234 break
235 }
236 }
237 if !foundMatch {
238 return errors.New(fmt.Sprint(
239 "Failed to find team ", s.TeamNumber,
240 " in match ", s.MatchNumber, " in the schedule."))
241 }
242
243 result := database.Create(&s)
244 return result.Error
245}
246
Emily Markova6b551e02023-02-18 17:37:40 -0800247func (database *Database) DeleteFromStats(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
248 var stats2023 []Stats2023
249 result := database.
250 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
251 Delete(&stats2023)
252 return result.Error
253}
254
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700255func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700256 result := database.Clauses(clause.OnConflict{
257 UpdateAll: true,
258 }).Create(&r)
259 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700260}
261
Emily Markovabf24c9e2023-02-08 20:31:11 -0800262func (database *Database) ReturnMatches() ([]TeamMatch, error) {
263 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700264 result := database.Find(&matches)
265 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800266}
267
Filip Kujawaf882e022022-12-14 13:14:08 -0800268func (database *Database) ReturnAllNotes() ([]NotesData, error) {
269 var notes []NotesData
270 result := database.Find(&notes)
271 return notes, result.Error
272}
273
274func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
275 var rankings []DriverRankingData
276 result := database.Find(&rankings)
277 return rankings, result.Error
278}
279
Philipp Schradera8955fb2023-03-05 15:47:19 -0800280func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
281 var rankings []ParsedDriverRankingData
282 result := database.Find(&rankings)
283 return rankings, result.Error
284}
285
Milo Lina72e2002022-04-06 20:31:13 -0700286func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700287 var shifts []Shift
288 result := database.Find(&shifts)
289 return shifts, result.Error
290}
Milo Lina72e2002022-04-06 20:31:13 -0700291
Sabina Leaver759090b2023-01-14 20:42:56 -0800292func (database *Database) ReturnActions() ([]Action, error) {
293 var actions []Action
294 result := database.Find(&actions)
295 return actions, result.Error
296}
297
Philipp Schradereecb8962022-06-01 21:02:42 -0700298// Packs the stats. This really just consists of taking the individual auto
299// ball booleans and turning them into an array. The individual booleans are
300// cleared so that they don't affect struct comparisons.
301func packStats(stats *Stats) {
302 stats.AutoBallPickedUp = [5]bool{
303 stats.AutoBallPickedUp1,
304 stats.AutoBallPickedUp2,
305 stats.AutoBallPickedUp3,
306 stats.AutoBallPickedUp4,
307 stats.AutoBallPickedUp5,
Milo Lina72e2002022-04-06 20:31:13 -0700308 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700309 stats.AutoBallPickedUp1 = false
310 stats.AutoBallPickedUp2 = false
311 stats.AutoBallPickedUp3 = false
312 stats.AutoBallPickedUp4 = false
313 stats.AutoBallPickedUp5 = false
Milo Lina72e2002022-04-06 20:31:13 -0700314}
315
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800316func (database *Database) ReturnStats() ([]Stats, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700317 var stats []Stats
318 result := database.Find(&stats)
319 // Pack the auto balls array.
320 for i := range stats {
321 packStats(&stats[i])
Philipp Schrader30005e42022-03-06 13:53:58 -0800322 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700323 return stats, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800324}
325
Emily Markova6b551e02023-02-18 17:37:40 -0800326func (database *Database) ReturnStats2023() ([]Stats2023, error) {
327 var stats2023 []Stats2023
328 result := database.Find(&stats2023)
329 return stats2023, result.Error
330}
331
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700332func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700333 var rankins []Ranking
334 result := database.Find(&rankins)
335 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700336}
337
Emily Markovabf24c9e2023-02-08 20:31:11 -0800338func (database *Database) queryMatches(teamNumber_ int32) ([]TeamMatch, error) {
339 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700340 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800341 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700342 Find(&matches)
343 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800344}
345
Emily Markovabf24c9e2023-02-08 20:31:11 -0800346func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
347 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800348 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800349 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800350 Find(&matches)
351 return matches, result.Error
352}
353
Milo Lina72e2002022-04-06 20:31:13 -0700354func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700355 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700356 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
357 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700358}
359
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800360func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700361 var stats []Stats
362 result := database.Where("team_number = ?", teamNumber_).Find(&stats)
363 // Pack the auto balls array.
364 for i := range stats {
365 packStats(&stats[i])
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800366 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700367 return stats, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800368}
Alex Perry871eab92022-03-12 17:43:52 -0800369
Sabina Leaver759090b2023-01-14 20:42:56 -0800370func (database *Database) QueryActions(teamNumber_ int) ([]Action, error) {
371 var actions []Action
372 result := database.
373 Where("team_number = ?", teamNumber_).Find(&actions)
374 return actions, result.Error
375}
376
Philipp Schradereecb8962022-06-01 21:02:42 -0700377func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
378 var rawNotes []NotesData
379 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
380 if result.Error != nil {
381 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800382 }
Alex Perry871eab92022-03-12 17:43:52 -0800383
Philipp Schradereecb8962022-06-01 21:02:42 -0700384 notes := make([]string, len(rawNotes))
385 for i := range rawNotes {
386 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800387 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700388 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800389}
390
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700391func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700392 var rankins []Ranking
393 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
394 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700395}
396
Filip Kujawaf947cb42022-11-21 10:00:30 -0800397func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700398 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800399 TeamNumber: data.TeamNumber,
400 Notes: data.Notes,
401 GoodDriving: data.GoodDriving,
402 BadDriving: data.BadDriving,
403 SketchyPickup: data.SketchyPickup,
404 SketchyPlacing: data.SketchyPlacing,
405 GoodDefense: data.GoodDefense,
406 BadDefense: data.BadDefense,
407 EasilyDefended: data.EasilyDefended,
Philipp Schradereecb8962022-06-01 21:02:42 -0700408 })
409 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800410}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800411
412func (database *Database) AddDriverRanking(data DriverRankingData) error {
413 result := database.Create(&DriverRankingData{
414 MatchNumber: data.MatchNumber,
415 Rank1: data.Rank1,
416 Rank2: data.Rank2,
417 Rank3: data.Rank3,
418 })
419 return result.Error
420}
421
Philipp Schradera8955fb2023-03-05 15:47:19 -0800422func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
423 result := database.Clauses(clause.OnConflict{
424 UpdateAll: true,
425 }).Create(&data)
426 return result.Error
427}
428
Filip Kujawa210a03b2022-11-24 14:41:11 -0800429func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
430 var data []DriverRankingData
431 result := database.Where("match_number = ?", MatchNumber).Find(&data)
432 return data, result.Error
433}