blob: ac9a6e8e0607a3ea838f7f138c8b6b43899dc581 [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
Emily Markovab8551572023-03-22 19:49:39 -070022 TeamNumber string
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 Leaver759090b2023-01-14 20:42:56 -080030type Stats2023 struct {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -070031 // This is set to `true` for "pre-scouted" matches. This means that the
32 // match information is unlikely to correspond with an entry in the
33 // `TeamMatch` table.
34 PreScouting bool `gorm:"primaryKey"`
35
Sabina Leaver759090b2023-01-14 20:42:56 -080036 TeamNumber string `gorm:"primaryKey"`
37 MatchNumber int32 `gorm:"primaryKey"`
38 SetNumber int32 `gorm:"primaryKey"`
39 CompLevel string `gorm:"primaryKey"`
40 StartingQuadrant int32
41 LowCubesAuto, MiddleCubesAuto, HighCubesAuto, CubesDroppedAuto int32
42 LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
43 LowCubes, MiddleCubes, HighCubes, CubesDropped int32
44 LowCones, MiddleCones, HighCones, ConesDropped int32
Filip Kujawa7a045e72023-04-13 08:41:09 -070045 SuperchargedPieces int32
Philipp Schrader8c878a22023-03-20 22:36:38 -070046 AvgCycle int64
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070047 Mobility bool
Emily Markova63c63f62023-03-29 20:57:35 -070048 DockedAuto, EngagedAuto, BalanceAttemptAuto bool
49 Docked, Engaged, BalanceAttempt bool
50
Sabina Leaver759090b2023-01-14 20:42:56 -080051 // The username of the person who collected these statistics.
52 // "unknown" if submitted without logging in.
53 // Empty if the stats have not yet been collected.
54 CollectedBy string
55}
56
57type Action struct {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -070058 PreScouting bool `gorm:"primaryKey"`
Sabina Leaver9b4eb312023-02-20 19:58:17 -080059 TeamNumber string `gorm:"primaryKey"`
60 MatchNumber int32 `gorm:"primaryKey"`
61 SetNumber int32 `gorm:"primaryKey"`
62 CompLevel string `gorm:"primaryKey"`
Sabina Leaver759090b2023-01-14 20:42:56 -080063 // This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
Sabina Leaver9b4eb312023-02-20 19:58:17 -080064 CompletedAction []byte
Philipp Schrader670a1c82023-05-17 19:42:43 -070065 Timestamp int64 `gorm:"primaryKey"`
66 CollectedBy string
Sabina Leaver759090b2023-01-14 20:42:56 -080067}
68
Alex Perry871eab92022-03-12 17:43:52 -080069type NotesData struct {
Filip Kujawa7ddd5652023-03-07 19:56:15 -080070 ID uint `gorm:"primaryKey"`
71 TeamNumber int32
72 Notes string
73 GoodDriving bool
74 BadDriving bool
Filip Kujawa11dc4c92023-04-13 08:55:43 -070075 SolidPlacing bool
Filip Kujawa7ddd5652023-03-07 19:56:15 -080076 SketchyPlacing bool
77 GoodDefense bool
78 BadDefense bool
79 EasilyDefended bool
Alex Perry871eab92022-03-12 17:43:52 -080080}
81
Yash Chainanibcd1bb32022-04-02 17:10:24 -070082type Ranking struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070083 TeamNumber int `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -070084 Losses, Wins, Ties int32
85 Rank, Dq int32
86}
87
Filip Kujawa210a03b2022-11-24 14:41:11 -080088type DriverRankingData struct {
89 // Each entry in the table is a single scout's ranking.
90 // Multiple scouts can submit a driver ranking for the same
91 // teams in the same match.
92 // The teams being ranked are stored in Rank1, Rank2, Rank3,
93 // Rank1 being the best driving and Rank3 being the worst driving.
94
95 ID uint `gorm:"primaryKey"`
96 MatchNumber int32
97 Rank1 int32
98 Rank2 int32
99 Rank3 int32
100}
101
Philipp Schradera8955fb2023-03-05 15:47:19 -0800102type ParsedDriverRankingData struct {
103 // This data stores the output of DriverRank.jl.
104
105 TeamNumber string `gorm:"primaryKey"`
106
107 // The score of the team. A difference of 100 in two team's scores
108 // indicates that one team will outperform the other in 90% of the
109 // matches.
110 Score float32
111}
112
Philipp Schrader7365d322022-03-06 16:40:08 -0800113// Opens a database at the specified port on localhost. We currently don't
114// support connecting to databases on other hosts.
115func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800116 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800117 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800118
Philipp Schradereecb8962022-06-01 21:02:42 -0700119 dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
120 database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
121 Logger: logger.Default.LogMode(logger.Silent),
122 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800123 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700124 database.Delete()
Philipp Schrader7365d322022-03-06 16:40:08 -0800125 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
126 }
Philipp Schrader36df73a2022-03-17 23:27:24 -0700127
Emily Markova132e5be2023-03-25 13:43:05 -0700128 err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2023{}, &Action{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
Philipp Schrader83fc2722022-03-10 21:59:20 -0800129 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700130 database.Delete()
131 return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700132 }
133
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800134 return database, nil
135}
136
137func (database *Database) Delete() error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700138 sql, err := database.DB.DB()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800139 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700140 return err
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800141 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700142 return sql.Close()
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800143}
144
Philipp Schradereecb8962022-06-01 21:02:42 -0700145func (database *Database) SetDebugLogLevel() {
146 database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
147}
Philipp Schradercd12c952022-04-08 18:58:49 -0700148
Emily Markovabf24c9e2023-02-08 20:31:11 -0800149func (database *Database) AddToMatch(m TeamMatch) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700150 result := database.Clauses(clause.OnConflict{
151 UpdateAll: true,
152 }).Create(&m)
153 return result.Error
Philipp Schradercd12c952022-04-08 18:58:49 -0700154}
155
Milo Lina72e2002022-04-06 20:31:13 -0700156func (database *Database) AddToShift(sh Shift) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700157 result := database.Clauses(clause.OnConflict{
158 UpdateAll: true,
159 }).Create(&sh)
160 return result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700161}
162
Sabina Leaver759090b2023-01-14 20:42:56 -0800163func (database *Database) AddAction(a Action) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700164 // TODO(phil): Add check for a corresponding match in the `TeamMatch`
165 // table. Similar to `AddToStats2023()` below.
166 result := database.Create(&a)
Sabina Leaver759090b2023-01-14 20:42:56 -0800167 return result.Error
168}
169
Sabina Leaver759090b2023-01-14 20:42:56 -0800170func (database *Database) AddToStats2023(s Stats2023) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700171 if !s.PreScouting {
172 matches, err := database.QueryMatchesString(s.TeamNumber)
173 if err != nil {
174 return err
Sabina Leaver759090b2023-01-14 20:42:56 -0800175 }
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700176 foundMatch := false
177 for _, match := range matches {
178 if match.MatchNumber == s.MatchNumber {
179 foundMatch = true
180 break
181 }
182 }
183 if !foundMatch {
184 return errors.New(fmt.Sprint(
185 "Failed to find team ", s.TeamNumber,
186 " in match ", s.MatchNumber, " in the schedule."))
187 }
Sabina Leaver759090b2023-01-14 20:42:56 -0800188 }
189
190 result := database.Create(&s)
191 return result.Error
192}
193
Emily Markova6b551e02023-02-18 17:37:40 -0800194func (database *Database) DeleteFromStats(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
195 var stats2023 []Stats2023
196 result := database.
197 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
198 Delete(&stats2023)
199 return result.Error
200}
201
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700202func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700203 result := database.Clauses(clause.OnConflict{
204 UpdateAll: true,
205 }).Create(&r)
206 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700207}
208
Emily Markovabf24c9e2023-02-08 20:31:11 -0800209func (database *Database) ReturnMatches() ([]TeamMatch, error) {
210 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700211 result := database.Find(&matches)
212 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800213}
214
Filip Kujawaf882e022022-12-14 13:14:08 -0800215func (database *Database) ReturnAllNotes() ([]NotesData, error) {
216 var notes []NotesData
217 result := database.Find(&notes)
218 return notes, result.Error
219}
220
221func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
222 var rankings []DriverRankingData
223 result := database.Find(&rankings)
224 return rankings, result.Error
225}
226
Philipp Schradera8955fb2023-03-05 15:47:19 -0800227func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
228 var rankings []ParsedDriverRankingData
229 result := database.Find(&rankings)
230 return rankings, result.Error
231}
232
Milo Lina72e2002022-04-06 20:31:13 -0700233func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700234 var shifts []Shift
235 result := database.Find(&shifts)
236 return shifts, result.Error
237}
Milo Lina72e2002022-04-06 20:31:13 -0700238
Sabina Leaver759090b2023-01-14 20:42:56 -0800239func (database *Database) ReturnActions() ([]Action, error) {
240 var actions []Action
241 result := database.Find(&actions)
242 return actions, result.Error
243}
244
Emily Markova6b551e02023-02-18 17:37:40 -0800245func (database *Database) ReturnStats2023() ([]Stats2023, error) {
246 var stats2023 []Stats2023
247 result := database.Find(&stats2023)
248 return stats2023, result.Error
249}
250
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700251func (database *Database) ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]Stats2023, error) {
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800252 var stats2023 []Stats2023
253 result := database.
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700254 Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND pre_scouting = ?",
255 teamNumber, matchNumber, setNumber, compLevel, preScouting).
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800256 Find(&stats2023)
257 return stats2023, result.Error
258}
259
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700260func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700261 var rankins []Ranking
262 result := database.Find(&rankins)
263 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700264}
265
Emily Markovab8551572023-03-22 19:49:39 -0700266func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800267 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700268 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800269 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700270 Find(&matches)
271 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800272}
273
Emily Markovabf24c9e2023-02-08 20:31:11 -0800274func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
275 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800276 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800277 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800278 Find(&matches)
279 return matches, result.Error
280}
281
Milo Lina72e2002022-04-06 20:31:13 -0700282func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700283 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700284 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
285 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700286}
287
Sabina Leaver759090b2023-01-14 20:42:56 -0800288func (database *Database) QueryActions(teamNumber_ int) ([]Action, error) {
289 var actions []Action
290 result := database.
291 Where("team_number = ?", teamNumber_).Find(&actions)
292 return actions, result.Error
293}
294
Philipp Schradereecb8962022-06-01 21:02:42 -0700295func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
296 var rawNotes []NotesData
297 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
298 if result.Error != nil {
299 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800300 }
Alex Perry871eab92022-03-12 17:43:52 -0800301
Philipp Schradereecb8962022-06-01 21:02:42 -0700302 notes := make([]string, len(rawNotes))
303 for i := range rawNotes {
304 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800305 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700306 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800307}
308
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700309func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700310 var rankins []Ranking
311 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
312 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700313}
314
Filip Kujawaf947cb42022-11-21 10:00:30 -0800315func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700316 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800317 TeamNumber: data.TeamNumber,
318 Notes: data.Notes,
319 GoodDriving: data.GoodDriving,
320 BadDriving: data.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700321 SolidPlacing: data.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800322 SketchyPlacing: data.SketchyPlacing,
323 GoodDefense: data.GoodDefense,
324 BadDefense: data.BadDefense,
325 EasilyDefended: data.EasilyDefended,
Philipp Schradereecb8962022-06-01 21:02:42 -0700326 })
327 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800328}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800329
330func (database *Database) AddDriverRanking(data DriverRankingData) error {
331 result := database.Create(&DriverRankingData{
332 MatchNumber: data.MatchNumber,
333 Rank1: data.Rank1,
334 Rank2: data.Rank2,
335 Rank3: data.Rank3,
336 })
337 return result.Error
338}
339
Philipp Schradera8955fb2023-03-05 15:47:19 -0800340func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
341 result := database.Clauses(clause.OnConflict{
342 UpdateAll: true,
343 }).Create(&data)
344 return result.Error
345}
346
Filip Kujawa210a03b2022-11-24 14:41:11 -0800347func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
348 var data []DriverRankingData
349 result := database.Where("match_number = ?", MatchNumber).Find(&data)
350 return data, result.Error
351}