blob: b4d1bcaebce67cd32e2017b469fc4a36dda52cda [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 Schraderba2c5a62023-04-16 22:12:21 -070065 // TODO(phil): Get all the spellings of "timestamp" to be the same.
66 TimeStamp int64 `gorm:"primaryKey"`
67 CollectedBy string
Sabina Leaver759090b2023-01-14 20:42:56 -080068}
69
Alex Perry871eab92022-03-12 17:43:52 -080070type NotesData struct {
Filip Kujawa7ddd5652023-03-07 19:56:15 -080071 ID uint `gorm:"primaryKey"`
72 TeamNumber int32
73 Notes string
74 GoodDriving bool
75 BadDriving bool
Filip Kujawa11dc4c92023-04-13 08:55:43 -070076 SolidPlacing bool
Filip Kujawa7ddd5652023-03-07 19:56:15 -080077 SketchyPlacing bool
78 GoodDefense bool
79 BadDefense bool
80 EasilyDefended bool
Alex Perry871eab92022-03-12 17:43:52 -080081}
82
Yash Chainanibcd1bb32022-04-02 17:10:24 -070083type Ranking struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070084 TeamNumber int `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -070085 Losses, Wins, Ties int32
86 Rank, Dq int32
87}
88
Filip Kujawa210a03b2022-11-24 14:41:11 -080089type DriverRankingData struct {
90 // Each entry in the table is a single scout's ranking.
91 // Multiple scouts can submit a driver ranking for the same
92 // teams in the same match.
93 // The teams being ranked are stored in Rank1, Rank2, Rank3,
94 // Rank1 being the best driving and Rank3 being the worst driving.
95
96 ID uint `gorm:"primaryKey"`
97 MatchNumber int32
98 Rank1 int32
99 Rank2 int32
100 Rank3 int32
101}
102
Philipp Schradera8955fb2023-03-05 15:47:19 -0800103type ParsedDriverRankingData struct {
104 // This data stores the output of DriverRank.jl.
105
106 TeamNumber string `gorm:"primaryKey"`
107
108 // The score of the team. A difference of 100 in two team's scores
109 // indicates that one team will outperform the other in 90% of the
110 // matches.
111 Score float32
112}
113
Philipp Schrader7365d322022-03-06 16:40:08 -0800114// Opens a database at the specified port on localhost. We currently don't
115// support connecting to databases on other hosts.
116func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800117 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800118 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800119
Philipp Schradereecb8962022-06-01 21:02:42 -0700120 dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
121 database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
122 Logger: logger.Default.LogMode(logger.Silent),
123 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800124 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700125 database.Delete()
Philipp Schrader7365d322022-03-06 16:40:08 -0800126 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
127 }
Philipp Schrader36df73a2022-03-17 23:27:24 -0700128
Emily Markova132e5be2023-03-25 13:43:05 -0700129 err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2023{}, &Action{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
Philipp Schrader83fc2722022-03-10 21:59:20 -0800130 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700131 database.Delete()
132 return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700133 }
134
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800135 return database, nil
136}
137
138func (database *Database) Delete() error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700139 sql, err := database.DB.DB()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800140 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700141 return err
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800142 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700143 return sql.Close()
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800144}
145
Philipp Schradereecb8962022-06-01 21:02:42 -0700146func (database *Database) SetDebugLogLevel() {
147 database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
148}
Philipp Schradercd12c952022-04-08 18:58:49 -0700149
Emily Markovabf24c9e2023-02-08 20:31:11 -0800150func (database *Database) AddToMatch(m TeamMatch) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700151 result := database.Clauses(clause.OnConflict{
152 UpdateAll: true,
153 }).Create(&m)
154 return result.Error
Philipp Schradercd12c952022-04-08 18:58:49 -0700155}
156
Milo Lina72e2002022-04-06 20:31:13 -0700157func (database *Database) AddToShift(sh Shift) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700158 result := database.Clauses(clause.OnConflict{
159 UpdateAll: true,
160 }).Create(&sh)
161 return result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700162}
163
Sabina Leaver759090b2023-01-14 20:42:56 -0800164func (database *Database) AddAction(a Action) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700165 // TODO(phil): Add check for a corresponding match in the `TeamMatch`
166 // table. Similar to `AddToStats2023()` below.
167 result := database.Create(&a)
Sabina Leaver759090b2023-01-14 20:42:56 -0800168 return result.Error
169}
170
Sabina Leaver759090b2023-01-14 20:42:56 -0800171func (database *Database) AddToStats2023(s Stats2023) error {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700172 if !s.PreScouting {
173 matches, err := database.QueryMatchesString(s.TeamNumber)
174 if err != nil {
175 return err
Sabina Leaver759090b2023-01-14 20:42:56 -0800176 }
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700177 foundMatch := false
178 for _, match := range matches {
179 if match.MatchNumber == s.MatchNumber {
180 foundMatch = true
181 break
182 }
183 }
184 if !foundMatch {
185 return errors.New(fmt.Sprint(
186 "Failed to find team ", s.TeamNumber,
187 " in match ", s.MatchNumber, " in the schedule."))
188 }
Sabina Leaver759090b2023-01-14 20:42:56 -0800189 }
190
191 result := database.Create(&s)
192 return result.Error
193}
194
Emily Markova6b551e02023-02-18 17:37:40 -0800195func (database *Database) DeleteFromStats(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
196 var stats2023 []Stats2023
197 result := database.
198 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
199 Delete(&stats2023)
200 return result.Error
201}
202
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700203func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700204 result := database.Clauses(clause.OnConflict{
205 UpdateAll: true,
206 }).Create(&r)
207 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700208}
209
Emily Markovabf24c9e2023-02-08 20:31:11 -0800210func (database *Database) ReturnMatches() ([]TeamMatch, error) {
211 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700212 result := database.Find(&matches)
213 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800214}
215
Filip Kujawaf882e022022-12-14 13:14:08 -0800216func (database *Database) ReturnAllNotes() ([]NotesData, error) {
217 var notes []NotesData
218 result := database.Find(&notes)
219 return notes, result.Error
220}
221
222func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
223 var rankings []DriverRankingData
224 result := database.Find(&rankings)
225 return rankings, result.Error
226}
227
Philipp Schradera8955fb2023-03-05 15:47:19 -0800228func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
229 var rankings []ParsedDriverRankingData
230 result := database.Find(&rankings)
231 return rankings, result.Error
232}
233
Milo Lina72e2002022-04-06 20:31:13 -0700234func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700235 var shifts []Shift
236 result := database.Find(&shifts)
237 return shifts, result.Error
238}
Milo Lina72e2002022-04-06 20:31:13 -0700239
Sabina Leaver759090b2023-01-14 20:42:56 -0800240func (database *Database) ReturnActions() ([]Action, error) {
241 var actions []Action
242 result := database.Find(&actions)
243 return actions, result.Error
244}
245
Emily Markova6b551e02023-02-18 17:37:40 -0800246func (database *Database) ReturnStats2023() ([]Stats2023, error) {
247 var stats2023 []Stats2023
248 result := database.Find(&stats2023)
249 return stats2023, result.Error
250}
251
Philipp Schrader78dc96b2023-03-11 15:23:44 -0800252func (database *Database) ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string) ([]Stats2023, error) {
253 var stats2023 []Stats2023
254 result := database.
255 Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ?",
256 teamNumber, matchNumber, setNumber, compLevel).
257 Find(&stats2023)
258 return stats2023, result.Error
259}
260
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700261func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700262 var rankins []Ranking
263 result := database.Find(&rankins)
264 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700265}
266
Emily Markovab8551572023-03-22 19:49:39 -0700267func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800268 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700269 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800270 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700271 Find(&matches)
272 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800273}
274
Emily Markovabf24c9e2023-02-08 20:31:11 -0800275func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
276 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800277 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800278 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800279 Find(&matches)
280 return matches, result.Error
281}
282
Milo Lina72e2002022-04-06 20:31:13 -0700283func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700284 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700285 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
286 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700287}
288
Sabina Leaver759090b2023-01-14 20:42:56 -0800289func (database *Database) QueryActions(teamNumber_ int) ([]Action, error) {
290 var actions []Action
291 result := database.
292 Where("team_number = ?", teamNumber_).Find(&actions)
293 return actions, result.Error
294}
295
Philipp Schradereecb8962022-06-01 21:02:42 -0700296func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
297 var rawNotes []NotesData
298 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
299 if result.Error != nil {
300 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800301 }
Alex Perry871eab92022-03-12 17:43:52 -0800302
Philipp Schradereecb8962022-06-01 21:02:42 -0700303 notes := make([]string, len(rawNotes))
304 for i := range rawNotes {
305 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800306 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700307 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800308}
309
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700310func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700311 var rankins []Ranking
312 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
313 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700314}
315
Filip Kujawaf947cb42022-11-21 10:00:30 -0800316func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700317 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800318 TeamNumber: data.TeamNumber,
319 Notes: data.Notes,
320 GoodDriving: data.GoodDriving,
321 BadDriving: data.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700322 SolidPlacing: data.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800323 SketchyPlacing: data.SketchyPlacing,
324 GoodDefense: data.GoodDefense,
325 BadDefense: data.BadDefense,
326 EasilyDefended: data.EasilyDefended,
Philipp Schradereecb8962022-06-01 21:02:42 -0700327 })
328 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800329}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800330
331func (database *Database) AddDriverRanking(data DriverRankingData) error {
332 result := database.Create(&DriverRankingData{
333 MatchNumber: data.MatchNumber,
334 Rank1: data.Rank1,
335 Rank2: data.Rank2,
336 Rank3: data.Rank3,
337 })
338 return result.Error
339}
340
Philipp Schradera8955fb2023-03-05 15:47:19 -0800341func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
342 result := database.Clauses(clause.OnConflict{
343 UpdateAll: true,
344 }).Create(&data)
345 return result.Error
346}
347
Filip Kujawa210a03b2022-11-24 14:41:11 -0800348func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
349 var data []DriverRankingData
350 result := database.Where("match_number = ?", MatchNumber).Find(&data)
351 return data, result.Error
352}