blob: cf1c8236a86270cfd7761cde4db66c446cd9f9d6 [file] [log] [blame]
Sabina Leaverc5fd2772022-01-29 17:00:23 -08001package db
2
3import (
Emily Markovafaecfe12023-07-01 12:40:03 -07004 "crypto/sha256"
Philipp Schrader30005e42022-03-06 13:53:58 -08005 "errors"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08006 "fmt"
Philipp Schradereecb8962022-06-01 21:02:42 -07007 "gorm.io/driver/postgres"
8 "gorm.io/gorm"
9 "gorm.io/gorm/clause"
10 "gorm.io/gorm/logger"
Sabina Leaverc5fd2772022-01-29 17:00:23 -080011)
12
13type Database struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070014 *gorm.DB
Sabina Leaverc5fd2772022-01-29 17:00:23 -080015}
16
Emily Markovabf24c9e2023-02-08 20:31:11 -080017type TeamMatch struct {
18 MatchNumber int32 `gorm:"primaryKey"`
19 SetNumber int32 `gorm:"primaryKey"`
20 CompLevel string `gorm:"primaryKey"`
21 Alliance string `gorm:"primaryKey"` // "R" or "B"
22 AlliancePosition int32 `gorm:"primaryKey"` // 1, 2, or 3
Emily Markovab8551572023-03-22 19:49:39 -070023 TeamNumber string
Sabina Leaverc5fd2772022-01-29 17:00:23 -080024}
25
Milo Lina72e2002022-04-06 20:31:13 -070026type Shift struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070027 MatchNumber int32 `gorm:"primaryKey"`
Milo Lina72e2002022-04-06 20:31:13 -070028 R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter string
29}
30
Emily Markovafaecfe12023-07-01 12:40:03 -070031type PitImage struct {
32 TeamNumber string `gorm:"primaryKey"`
33 CheckSum string `gorm:"primaryKey"`
34 ImagePath string
35 ImageData []byte
36}
37
38type RequestedPitImage struct {
39 TeamNumber string
40 CheckSum string `gorm:"primaryKey"`
41 ImagePath string
42}
43
Sabina Leaver759090b2023-01-14 20:42:56 -080044type Stats2023 struct {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -070045 // This is set to `true` for "pre-scouted" matches. This means that the
46 // match information is unlikely to correspond with an entry in the
47 // `TeamMatch` table.
48 PreScouting bool `gorm:"primaryKey"`
49
Sabina Leaver759090b2023-01-14 20:42:56 -080050 TeamNumber string `gorm:"primaryKey"`
51 MatchNumber int32 `gorm:"primaryKey"`
52 SetNumber int32 `gorm:"primaryKey"`
53 CompLevel string `gorm:"primaryKey"`
54 StartingQuadrant int32
55 LowCubesAuto, MiddleCubesAuto, HighCubesAuto, CubesDroppedAuto int32
56 LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
57 LowCubes, MiddleCubes, HighCubes, CubesDropped int32
58 LowCones, MiddleCones, HighCones, ConesDropped int32
Filip Kujawa7a045e72023-04-13 08:41:09 -070059 SuperchargedPieces int32
Philipp Schrader8c878a22023-03-20 22:36:38 -070060 AvgCycle int64
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070061 Mobility bool
Emily Markova63c63f62023-03-29 20:57:35 -070062 DockedAuto, EngagedAuto, BalanceAttemptAuto bool
63 Docked, Engaged, BalanceAttempt bool
64
Sabina Leaver759090b2023-01-14 20:42:56 -080065 // The username of the person who collected these statistics.
66 // "unknown" if submitted without logging in.
67 // Empty if the stats have not yet been collected.
68 CollectedBy string
69}
70
71type Action struct {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -070072 PreScouting bool `gorm:"primaryKey"`
Sabina Leaver9b4eb312023-02-20 19:58:17 -080073 TeamNumber string `gorm:"primaryKey"`
74 MatchNumber int32 `gorm:"primaryKey"`
75 SetNumber int32 `gorm:"primaryKey"`
76 CompLevel string `gorm:"primaryKey"`
Sabina Leaver759090b2023-01-14 20:42:56 -080077 // This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
Sabina Leaver9b4eb312023-02-20 19:58:17 -080078 CompletedAction []byte
Philipp Schrader670a1c82023-05-17 19:42:43 -070079 Timestamp int64 `gorm:"primaryKey"`
80 CollectedBy string
Sabina Leaver759090b2023-01-14 20:42:56 -080081}
82
Alex Perry871eab92022-03-12 17:43:52 -080083type NotesData struct {
Filip Kujawa7ddd5652023-03-07 19:56:15 -080084 ID uint `gorm:"primaryKey"`
Emily Markovae68b7632023-12-30 14:17:55 -080085 TeamNumber string
Filip Kujawa7ddd5652023-03-07 19:56:15 -080086 Notes string
87 GoodDriving bool
88 BadDriving bool
Filip Kujawa11dc4c92023-04-13 08:55:43 -070089 SolidPlacing bool
Filip Kujawa7ddd5652023-03-07 19:56:15 -080090 SketchyPlacing bool
91 GoodDefense bool
92 BadDefense bool
93 EasilyDefended bool
Alex Perry871eab92022-03-12 17:43:52 -080094}
95
Yash Chainanibcd1bb32022-04-02 17:10:24 -070096type Ranking struct {
Emily Markovae68b7632023-12-30 14:17:55 -080097 TeamNumber string `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -070098 Losses, Wins, Ties int32
99 Rank, Dq int32
100}
101
Filip Kujawa210a03b2022-11-24 14:41:11 -0800102type DriverRankingData struct {
103 // Each entry in the table is a single scout's ranking.
104 // Multiple scouts can submit a driver ranking for the same
105 // teams in the same match.
106 // The teams being ranked are stored in Rank1, Rank2, Rank3,
107 // Rank1 being the best driving and Rank3 being the worst driving.
108
109 ID uint `gorm:"primaryKey"`
110 MatchNumber int32
Emily Markovae68b7632023-12-30 14:17:55 -0800111 Rank1 string
112 Rank2 string
113 Rank3 string
Filip Kujawa210a03b2022-11-24 14:41:11 -0800114}
115
Philipp Schradera8955fb2023-03-05 15:47:19 -0800116type ParsedDriverRankingData struct {
117 // This data stores the output of DriverRank.jl.
118
119 TeamNumber string `gorm:"primaryKey"`
120
121 // The score of the team. A difference of 100 in two team's scores
122 // indicates that one team will outperform the other in 90% of the
123 // matches.
124 Score float32
125}
126
Philipp Schrader7365d322022-03-06 16:40:08 -0800127// Opens a database at the specified port on localhost. We currently don't
128// support connecting to databases on other hosts.
129func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800130 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800131 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800132
Philipp Schradereecb8962022-06-01 21:02:42 -0700133 dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
134 database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
135 Logger: logger.Default.LogMode(logger.Silent),
136 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800137 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700138 database.Delete()
Philipp Schrader7365d322022-03-06 16:40:08 -0800139 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
140 }
Philipp Schrader36df73a2022-03-17 23:27:24 -0700141
Emily Markovafaecfe12023-07-01 12:40:03 -0700142 err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2023{}, &Action{}, &PitImage{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
Philipp Schrader83fc2722022-03-10 21:59:20 -0800143 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700144 database.Delete()
145 return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700146 }
147
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800148 return database, nil
149}
150
151func (database *Database) Delete() error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700152 sql, err := database.DB.DB()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800153 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700154 return err
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800155 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700156 return sql.Close()
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800157}
158
Philipp Schradereecb8962022-06-01 21:02:42 -0700159func (database *Database) SetDebugLogLevel() {
160 database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
161}
Philipp Schradercd12c952022-04-08 18:58:49 -0700162
Emily Markovabf24c9e2023-02-08 20:31:11 -0800163func (database *Database) AddToMatch(m TeamMatch) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700164 result := database.Clauses(clause.OnConflict{
165 UpdateAll: true,
166 }).Create(&m)
167 return result.Error
Philipp Schradercd12c952022-04-08 18:58:49 -0700168}
169
Milo Lina72e2002022-04-06 20:31:13 -0700170func (database *Database) AddToShift(sh Shift) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700171 result := database.Clauses(clause.OnConflict{
172 UpdateAll: true,
173 }).Create(&sh)
174 return result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700175}
176
Sabina Leaver759090b2023-01-14 20:42:56 -0800177func (database *Database) AddAction(a Action) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700178 // TODO(phil): Add check for a corresponding match in the `TeamMatch`
179 // table. Similar to `AddToStats2023()` below.
180 result := database.Create(&a)
Sabina Leaver759090b2023-01-14 20:42:56 -0800181 return result.Error
182}
183
Emily Markovafaecfe12023-07-01 12:40:03 -0700184func (database *Database) AddPitImage(p PitImage) error {
185 result := database.Create(&p)
186 return result.Error
187}
188
Sabina Leaver759090b2023-01-14 20:42:56 -0800189func (database *Database) AddToStats2023(s Stats2023) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700190 if !s.PreScouting {
191 matches, err := database.QueryMatchesString(s.TeamNumber)
192 if err != nil {
193 return err
Sabina Leaver759090b2023-01-14 20:42:56 -0800194 }
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700195 foundMatch := false
196 for _, match := range matches {
197 if match.MatchNumber == s.MatchNumber {
198 foundMatch = true
199 break
200 }
201 }
202 if !foundMatch {
203 return errors.New(fmt.Sprint(
204 "Failed to find team ", s.TeamNumber,
205 " in match ", s.MatchNumber, " in the schedule."))
206 }
Sabina Leaver759090b2023-01-14 20:42:56 -0800207 }
208
209 result := database.Create(&s)
210 return result.Error
211}
212
Emily Markova6b551e02023-02-18 17:37:40 -0800213func (database *Database) DeleteFromStats(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
214 var stats2023 []Stats2023
215 result := database.
216 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
217 Delete(&stats2023)
218 return result.Error
219}
220
Filip Kujawac1ded372023-05-27 14:33:43 -0700221func (database *Database) DeleteFromActions(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
222 var actions []Action
223 result := database.
224 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
225 Delete(&actions)
226 return result.Error
227}
228
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700229func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700230 result := database.Clauses(clause.OnConflict{
231 UpdateAll: true,
232 }).Create(&r)
233 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700234}
235
Emily Markovabf24c9e2023-02-08 20:31:11 -0800236func (database *Database) ReturnMatches() ([]TeamMatch, error) {
237 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700238 result := database.Find(&matches)
239 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800240}
241
Filip Kujawaf882e022022-12-14 13:14:08 -0800242func (database *Database) ReturnAllNotes() ([]NotesData, error) {
243 var notes []NotesData
244 result := database.Find(&notes)
245 return notes, result.Error
246}
247
248func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
249 var rankings []DriverRankingData
250 result := database.Find(&rankings)
251 return rankings, result.Error
252}
253
Philipp Schradera8955fb2023-03-05 15:47:19 -0800254func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
255 var rankings []ParsedDriverRankingData
256 result := database.Find(&rankings)
257 return rankings, result.Error
258}
259
Milo Lina72e2002022-04-06 20:31:13 -0700260func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700261 var shifts []Shift
262 result := database.Find(&shifts)
263 return shifts, result.Error
264}
Milo Lina72e2002022-04-06 20:31:13 -0700265
Sabina Leaver759090b2023-01-14 20:42:56 -0800266func (database *Database) ReturnActions() ([]Action, error) {
267 var actions []Action
268 result := database.Find(&actions)
269 return actions, result.Error
270}
271
Emily Markovafaecfe12023-07-01 12:40:03 -0700272func (database *Database) ReturnPitImages() ([]PitImage, error) {
273 var images []PitImage
274 result := database.Find(&images)
275 return images, result.Error
276}
277
Emily Markova6b551e02023-02-18 17:37:40 -0800278func (database *Database) ReturnStats2023() ([]Stats2023, error) {
279 var stats2023 []Stats2023
280 result := database.Find(&stats2023)
281 return stats2023, result.Error
282}
283
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700284func (database *Database) ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]Stats2023, error) {
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800285 var stats2023 []Stats2023
286 result := database.
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700287 Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND pre_scouting = ?",
288 teamNumber, matchNumber, setNumber, compLevel, preScouting).
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800289 Find(&stats2023)
290 return stats2023, result.Error
291}
292
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700293func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700294 var rankins []Ranking
295 result := database.Find(&rankins)
296 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700297}
298
Emily Markovab8551572023-03-22 19:49:39 -0700299func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800300 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700301 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800302 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700303 Find(&matches)
304 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800305}
306
Emily Markovafaecfe12023-07-01 12:40:03 -0700307func (database *Database) QueryPitImages(teamNumber_ string) ([]RequestedPitImage, error) {
308 var requestedPitImages []RequestedPitImage
309 result := database.Model(&PitImage{}).
310 Where("team_number = $1", teamNumber_).
311 Find(&requestedPitImages)
312
313 return requestedPitImages, result.Error
314}
315
316func (database *Database) QueryPitImageByChecksum(checksum_ string) (PitImage, error) {
317 var pitImage PitImage
318 result := database.
319 Where("check_sum = $1", checksum_).
320 Find(&pitImage)
321 return pitImage, result.Error
322}
323
324func ComputeSha256FromByteArray(arr []byte) string {
325 sum := sha256.Sum256(arr)
326 return fmt.Sprintf("%x", sum)
327}
328
Emily Markovabf24c9e2023-02-08 20:31:11 -0800329func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
330 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800331 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800332 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800333 Find(&matches)
334 return matches, result.Error
335}
336
Milo Lina72e2002022-04-06 20:31:13 -0700337func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700338 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700339 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
340 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700341}
342
Emily Markovae68b7632023-12-30 14:17:55 -0800343func (database *Database) QueryActions(teamNumber_ string) ([]Action, error) {
Sabina Leaver759090b2023-01-14 20:42:56 -0800344 var actions []Action
345 result := database.
346 Where("team_number = ?", teamNumber_).Find(&actions)
347 return actions, result.Error
348}
349
Emily Markovae68b7632023-12-30 14:17:55 -0800350func (database *Database) QueryNotes(TeamNumber string) ([]string, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700351 var rawNotes []NotesData
352 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
353 if result.Error != nil {
354 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800355 }
Alex Perry871eab92022-03-12 17:43:52 -0800356
Philipp Schradereecb8962022-06-01 21:02:42 -0700357 notes := make([]string, len(rawNotes))
358 for i := range rawNotes {
359 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800360 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700361 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800362}
363
Emily Markovae68b7632023-12-30 14:17:55 -0800364func (database *Database) QueryRankings(TeamNumber string) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700365 var rankins []Ranking
366 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
367 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700368}
369
Filip Kujawaf947cb42022-11-21 10:00:30 -0800370func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700371 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800372 TeamNumber: data.TeamNumber,
373 Notes: data.Notes,
374 GoodDriving: data.GoodDriving,
375 BadDriving: data.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700376 SolidPlacing: data.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800377 SketchyPlacing: data.SketchyPlacing,
378 GoodDefense: data.GoodDefense,
379 BadDefense: data.BadDefense,
380 EasilyDefended: data.EasilyDefended,
Philipp Schradereecb8962022-06-01 21:02:42 -0700381 })
382 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800383}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800384
385func (database *Database) AddDriverRanking(data DriverRankingData) error {
386 result := database.Create(&DriverRankingData{
387 MatchNumber: data.MatchNumber,
388 Rank1: data.Rank1,
389 Rank2: data.Rank2,
390 Rank3: data.Rank3,
391 })
392 return result.Error
393}
394
Philipp Schradera8955fb2023-03-05 15:47:19 -0800395func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
396 result := database.Clauses(clause.OnConflict{
397 UpdateAll: true,
398 }).Create(&data)
399 return result.Error
400}
401
Filip Kujawa210a03b2022-11-24 14:41:11 -0800402func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
403 var data []DriverRankingData
404 result := database.Where("match_number = ?", MatchNumber).Find(&data)
405 return data, result.Error
406}