blob: 1a436342129461b9c80e38588f16fd79789af004 [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
Filip Kujawac1ded372023-05-27 14:33:43 -0700202func (database *Database) DeleteFromActions(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
203 var actions []Action
204 result := database.
205 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
206 Delete(&actions)
207 return result.Error
208}
209
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700210func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700211 result := database.Clauses(clause.OnConflict{
212 UpdateAll: true,
213 }).Create(&r)
214 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700215}
216
Emily Markovabf24c9e2023-02-08 20:31:11 -0800217func (database *Database) ReturnMatches() ([]TeamMatch, error) {
218 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700219 result := database.Find(&matches)
220 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800221}
222
Filip Kujawaf882e022022-12-14 13:14:08 -0800223func (database *Database) ReturnAllNotes() ([]NotesData, error) {
224 var notes []NotesData
225 result := database.Find(&notes)
226 return notes, result.Error
227}
228
229func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
230 var rankings []DriverRankingData
231 result := database.Find(&rankings)
232 return rankings, result.Error
233}
234
Philipp Schradera8955fb2023-03-05 15:47:19 -0800235func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
236 var rankings []ParsedDriverRankingData
237 result := database.Find(&rankings)
238 return rankings, result.Error
239}
240
Milo Lina72e2002022-04-06 20:31:13 -0700241func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700242 var shifts []Shift
243 result := database.Find(&shifts)
244 return shifts, result.Error
245}
Milo Lina72e2002022-04-06 20:31:13 -0700246
Sabina Leaver759090b2023-01-14 20:42:56 -0800247func (database *Database) ReturnActions() ([]Action, error) {
248 var actions []Action
249 result := database.Find(&actions)
250 return actions, result.Error
251}
252
Emily Markova6b551e02023-02-18 17:37:40 -0800253func (database *Database) ReturnStats2023() ([]Stats2023, error) {
254 var stats2023 []Stats2023
255 result := database.Find(&stats2023)
256 return stats2023, result.Error
257}
258
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700259func (database *Database) ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]Stats2023, error) {
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800260 var stats2023 []Stats2023
261 result := database.
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700262 Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND pre_scouting = ?",
263 teamNumber, matchNumber, setNumber, compLevel, preScouting).
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800264 Find(&stats2023)
265 return stats2023, result.Error
266}
267
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700268func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700269 var rankins []Ranking
270 result := database.Find(&rankins)
271 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700272}
273
Emily Markovab8551572023-03-22 19:49:39 -0700274func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800275 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700276 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800277 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700278 Find(&matches)
279 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800280}
281
Emily Markovabf24c9e2023-02-08 20:31:11 -0800282func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
283 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800284 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800285 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800286 Find(&matches)
287 return matches, result.Error
288}
289
Milo Lina72e2002022-04-06 20:31:13 -0700290func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700291 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700292 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
293 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700294}
295
Sabina Leaver759090b2023-01-14 20:42:56 -0800296func (database *Database) QueryActions(teamNumber_ int) ([]Action, error) {
297 var actions []Action
298 result := database.
299 Where("team_number = ?", teamNumber_).Find(&actions)
300 return actions, result.Error
301}
302
Philipp Schradereecb8962022-06-01 21:02:42 -0700303func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
304 var rawNotes []NotesData
305 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
306 if result.Error != nil {
307 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800308 }
Alex Perry871eab92022-03-12 17:43:52 -0800309
Philipp Schradereecb8962022-06-01 21:02:42 -0700310 notes := make([]string, len(rawNotes))
311 for i := range rawNotes {
312 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800313 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700314 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800315}
316
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700317func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700318 var rankins []Ranking
319 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
320 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700321}
322
Filip Kujawaf947cb42022-11-21 10:00:30 -0800323func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700324 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800325 TeamNumber: data.TeamNumber,
326 Notes: data.Notes,
327 GoodDriving: data.GoodDriving,
328 BadDriving: data.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700329 SolidPlacing: data.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800330 SketchyPlacing: data.SketchyPlacing,
331 GoodDefense: data.GoodDefense,
332 BadDefense: data.BadDefense,
333 EasilyDefended: data.EasilyDefended,
Philipp Schradereecb8962022-06-01 21:02:42 -0700334 })
335 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800336}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800337
338func (database *Database) AddDriverRanking(data DriverRankingData) error {
339 result := database.Create(&DriverRankingData{
340 MatchNumber: data.MatchNumber,
341 Rank1: data.Rank1,
342 Rank2: data.Rank2,
343 Rank3: data.Rank3,
344 })
345 return result.Error
346}
347
Philipp Schradera8955fb2023-03-05 15:47:19 -0800348func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
349 result := database.Clauses(clause.OnConflict{
350 UpdateAll: true,
351 }).Create(&data)
352 return result.Error
353}
354
Filip Kujawa210a03b2022-11-24 14:41:11 -0800355func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
356 var data []DriverRankingData
357 result := database.Where("match_number = ?", MatchNumber).Find(&data)
358 return data, result.Error
359}