Scouting: Add Practice Match to Data Entry

I also deleted some tests from last year that used an old version of Action from db.go since they were causing problems.
I probably need to go through and delete other old database functions, etc in a future patch.

Signed-off-by: Emily Markova <emily.markova@gmail.com>
Change-Id: Ib13dbbbde901cc3e7c9aaf4d242eb6bac714aa3a
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 8c420a2..a98d2b2 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -69,15 +69,11 @@
 }
 
 type Stats2024 struct {
-	// This is set to `true` for "pre-scouted" matches. This means that the
-	// match information is unlikely to correspond with an entry in the
-	// `TeamMatch` table.
-	PreScouting bool `gorm:"primaryKey"`
-
 	TeamNumber                                  string `gorm:"primaryKey"`
 	MatchNumber                                 int32  `gorm:"primaryKey"`
 	SetNumber                                   int32  `gorm:"primaryKey"`
 	CompLevel                                   string `gorm:"primaryKey"`
+	CompType                                    string `gorm:"primaryKey"`
 	StartingQuadrant                            int32
 	SpeakerAuto, AmpAuto                        int32
 	NotesDroppedAuto                            int32
@@ -97,11 +93,11 @@
 }
 
 type Action struct {
-	PreScouting bool   `gorm:"primaryKey"`
 	TeamNumber  string `gorm:"primaryKey"`
 	MatchNumber int32  `gorm:"primaryKey"`
 	SetNumber   int32  `gorm:"primaryKey"`
 	CompLevel   string `gorm:"primaryKey"`
+	CompType    string `gorm:"primaryKey"`
 	// This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
 	CompletedAction []byte
 	Timestamp       int64 `gorm:"primaryKey"`
@@ -243,7 +239,7 @@
 }
 
 func (database *Database) AddToStats2024(s Stats2024) error {
-	if !s.PreScouting {
+	if s.CompType == "Regular" {
 		matches, err := database.QueryMatchesString(s.TeamNumber)
 		if err != nil {
 			return err
@@ -360,11 +356,11 @@
 	return stats2023, result.Error
 }
 
-func (database *Database) ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]Stats2024, error) {
+func (database *Database) ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, compType string) ([]Stats2024, error) {
 	var stats2024 []Stats2024
 	result := database.
-		Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND pre_scouting = ?",
-			teamNumber, matchNumber, setNumber, compLevel, preScouting).
+		Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND comp_type = ?",
+			teamNumber, matchNumber, setNumber, compLevel, compType).
 		Find(&stats2024)
 	return stats2024, result.Error
 }
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index bccfea1..ffb9817 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -144,7 +144,7 @@
 
 	correct := []Stats2024{
 		Stats2024{
-			PreScouting: false, TeamNumber: "894",
+			CompType: "Regular", TeamNumber: "894",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 1,
@@ -152,7 +152,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "942",
+			CompType: "Regular", TeamNumber: "942",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 			SpeakerAuto: 2, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
@@ -160,7 +160,15 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "432",
+			CompType: "Practice", TeamNumber: "942",
+			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 3,
+			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
+			Speaker: 2, Amp: 1, SpeakerAmplified: 3,
+			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "kaleb",
+		},
+		Stats2024{
+			CompType: "Regular", TeamNumber: "432",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 3,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 2, Amp: 1, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 2,
@@ -168,7 +176,7 @@
 			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "henry",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "52A",
+			CompType: "Regular", TeamNumber: "52A",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
@@ -176,7 +184,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "jordan",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "745",
+			CompType: "Regular", TeamNumber: "745",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 5, Amp: 0, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
@@ -184,7 +192,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "taylor",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "934",
+			CompType: "Regular", TeamNumber: "934",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 3,
 			SpeakerAuto: 1, AmpAuto: 3, NotesDroppedAuto: 0, MobilityAuto: true,
 			Speaker: 0, Amp: 3, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
@@ -231,7 +239,7 @@
 	defer fixture.TearDown()
 
 	stats := Stats2024{
-		PreScouting: false, TeamNumber: "6344",
+		CompType: "Regular", TeamNumber: "6344",
 		MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 		SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 		Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 1,
@@ -249,7 +257,35 @@
 	}
 
 	// Mark the data as pre-scouting data. It should now succeed.
-	stats.PreScouting = true
+	stats.CompType = "Prescouting"
+	err = fixture.db.AddToStats2024(stats)
+	check(t, err, "Failed to add prescouted stats to DB")
+}
+
+func TestInsertPracticeMatchStats2024(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	stats := Stats2024{
+		CompType: "Regular", TeamNumber: "6344",
+		MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
+		SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
+		Speaker: 0, Amp: 5, SpeakerAmplified: 1,
+		NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
+		Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
+	}
+
+	// Attempt to insert the non-practice match data and make sure it fails.
+	err := fixture.db.AddToStats2024(stats)
+	if err == nil {
+		t.Fatal("Expected error from inserting the stats.")
+	}
+	if err.Error() != "Failed to find team 6344 in match 3 in the schedule." {
+		t.Fatal("Got:", err.Error())
+	}
+
+	// Mark the data as practice match data. It should now succeed.
+	stats.CompType = "Practice"
 	err = fixture.db.AddToStats2024(stats)
 	check(t, err, "Failed to add prescouted stats to DB")
 }
@@ -260,7 +296,7 @@
 
 	stats := []Stats2024{
 		Stats2024{
-			PreScouting: false, TeamNumber: "328A",
+			CompType: "Regular", TeamNumber: "328A",
 			MatchNumber: 7, SetNumber: 1, CompLevel: "qm", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 2, OutOfField: 0,
@@ -268,7 +304,7 @@
 			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "emma",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "978",
+			CompType: "Regular", TeamNumber: "978",
 			MatchNumber: 2, SetNumber: 2, CompLevel: "qm", StartingQuadrant: 4,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 1, Amp: 2, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
@@ -276,7 +312,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "328A",
+			CompType: "Regular", TeamNumber: "328A",
 			MatchNumber: 4, SetNumber: 1, CompLevel: "qm", StartingQuadrant: 2,
 			SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
 			Speaker: 0, Amp: 1, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 1,
@@ -306,7 +342,7 @@
 
 	// Validate that requesting status for a single team gets us the
 	// expected data.
-	statsFor328A, err := fixture.db.ReturnStats2024ForTeam("328A", 7, 1, "qm", false)
+	statsFor328A, err := fixture.db.ReturnStats2024ForTeam("328A", 7, 1, "qm", "Regular")
 	check(t, err, "Failed ReturnStats2024()")
 
 	if !reflect.DeepEqual([]Stats2024{stats[0]}, statsFor328A) {
@@ -314,7 +350,7 @@
 	}
 	// Validate that requesting team data for a non-existent match returns
 	// nothing.
-	statsForMissing, err := fixture.db.ReturnStats2024ForTeam("6344", 9, 1, "qm", false)
+	statsForMissing, err := fixture.db.ReturnStats2024ForTeam("6344", 9, 1, "qm", "Regular")
 	check(t, err, "Failed ReturnStats2024()")
 
 	if !reflect.DeepEqual([]Stats2024{}, statsForMissing) {
@@ -695,7 +731,7 @@
 
 	startingStats := []Stats2024{
 		Stats2024{
-			PreScouting: false, TeamNumber: "345",
+			CompType: "Regular", TeamNumber: "345",
 			MatchNumber: 5, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 1, Amp: 3, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 3,
@@ -703,7 +739,7 @@
 			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "bailey",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "645",
+			CompType: "Practice", TeamNumber: "645",
 			MatchNumber: 5, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 1, Amp: 2, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
@@ -711,7 +747,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "kate",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "323",
+			CompType: "Regular", TeamNumber: "323",
 			MatchNumber: 5, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 			SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
 			Speaker: 0, Amp: 0, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 2,
@@ -719,7 +755,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "tyler",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "542",
+			CompType: "Regular", TeamNumber: "542",
 			MatchNumber: 5, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 1, Amp: 2, SpeakerAmplified: 2, Shuttled: 2, OutOfField: 2,
@@ -730,7 +766,7 @@
 
 	correct := []Stats2024{
 		Stats2024{
-			PreScouting: false, TeamNumber: "345",
+			CompType: "Regular", TeamNumber: "345",
 			MatchNumber: 5, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 1, Amp: 3, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 3,
@@ -1149,7 +1185,7 @@
 
 	correct := []Stats2024{
 		Stats2024{
-			PreScouting: false, TeamNumber: "894",
+			CompType: "Practice", TeamNumber: "894",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
@@ -1157,7 +1193,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "942",
+			CompType: "Regular", TeamNumber: "942",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 			SpeakerAuto: 2, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
@@ -1165,7 +1201,7 @@
 			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "432",
+			CompType: "Practice", TeamNumber: "432",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 3,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
 			Speaker: 2, Amp: 1, SpeakerAmplified: 3, Shuttled: 5, OutOfField: 1,
@@ -1173,7 +1209,7 @@
 			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "henry",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "52A",
+			CompType: "Regular", TeamNumber: "52A",
 			MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
diff --git a/scouting/scouting_test.cy.js b/scouting/scouting_test.cy.js
index e4c4ead..2727104 100644
--- a/scouting/scouting_test.cy.js
+++ b/scouting/scouting_test.cy.js
@@ -187,6 +187,21 @@
     headerShouldBe('1 Init ');
   });
 
+  it('should: allow users to scout practice matches.', () => {
+    switchToTab('Entry');
+    headerShouldBe(' Team Selection ');
+    setInputTo('#team_number', '1');
+
+    // The default team information should be invalid.
+    cy.contains('button', 'Next').should('be.disabled');
+
+    // Click the checkmark to designate this as pre-scouting.
+    // We should now be able to continue scouting.
+    cy.get('#practice_match').click();
+    clickButton('Next');
+    headerShouldBe('1 Init ');
+  });
+
   it('should: allow users to submit pit images.', () => {
     switchToTab('Pit');
     headerShouldBe('Pit Scouting');
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 846414c..07ebbe6 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -56,7 +56,6 @@
     deps = [
         "//scouting/db",
         "//scouting/webserver/requests/debug",
-        "//scouting/webserver/requests/messages:delete_2023_data_scouting_go_fbs",
         "//scouting/webserver/requests/messages:delete_2024_data_scouting_go_fbs",
         "//scouting/webserver/requests/messages:request_2023_data_scouting_go_fbs",
         "//scouting/webserver/requests/messages:request_2023_data_scouting_response_go_fbs",
diff --git a/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
index 7e4b58e..18416fb 100644
--- a/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
@@ -30,8 +30,9 @@
   spotlight: bool (id:22);
   robot_died: bool (id:23);
 
-  pre_scouting:bool (id:20);
+  pre_scouting:bool (id:20, deprecated);
   collected_by:string (id:21);
+  comp_type:string (id:26);
 }
 
 table Request2024DataScoutingResponse {
diff --git a/scouting/webserver/requests/messages/submit_2024_actions.fbs b/scouting/webserver/requests/messages/submit_2024_actions.fbs
index 08ff012..8d43afb 100644
--- a/scouting/webserver/requests/messages/submit_2024_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_2024_actions.fbs
@@ -77,8 +77,6 @@
     comp_level:string (id: 3);
     actions_list:[Action] (id:4);
 
-    // If this is for pre-scouting, then the server should accept this
-    // submission. I.e. checking that the match information exists in the match
-    // list should be skipped.
-    pre_scouting:bool (id: 5);
+    pre_scouting:bool (id: 5, deprecated);
+    comp_type:string (id: 6);
 }
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 01f1937..8761b7d 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -102,7 +102,7 @@
 	ReturnStats2023() ([]db.Stats2023, error)
 	ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2023, error)
 	ReturnStats2024() ([]db.Stats2024, error)
-	ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2024, error)
+	ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, compType string) ([]db.Stats2024, error)
 	QueryAllShifts(int) ([]db.Shift, error)
 	QueryNotes(string) ([]string, error)
 	QueryPitImages(string) ([]db.RequestedPitImage, error)
@@ -200,7 +200,7 @@
 
 func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
 	stats, err := handler.db.ReturnStats2024ForTeam(
-		teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel, false)
+		teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel, "Regular")
 	if err != nil {
 		return false, err
 	}
@@ -453,7 +453,8 @@
 	picked_up := false
 	lastPlacedTime := int64(0)
 	stat := db.Stats2024{
-		PreScouting: submit2024Actions.PreScouting(), TeamNumber: string(submit2024Actions.TeamNumber()), MatchNumber: submit2024Actions.MatchNumber(), SetNumber: submit2024Actions.SetNumber(), CompLevel: string(submit2024Actions.CompLevel()),
+		CompType: string(submit2024Actions.CompType()), TeamNumber: string(submit2024Actions.TeamNumber()),
+		MatchNumber: submit2024Actions.MatchNumber(), SetNumber: submit2024Actions.SetNumber(), CompLevel: string(submit2024Actions.CompLevel()),
 		StartingQuadrant: 0, SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 		Speaker: 0, Amp: 0, SpeakerAmplified: 0, NotesDropped: 0, Shuttled: 0, OutOfField: 0, Penalties: 0,
 		TrapNote: false, Spotlight: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "",
@@ -479,7 +480,6 @@
 			if mobilityAction.Mobility() {
 				stat.MobilityAuto = true
 			}
-
 		} else if action_type == submit_2024_actions.ActionTypePenaltyAction {
 			var penaltyAction submit_2024_actions.PenaltyAction
 			penaltyAction.Init(actionTable.Bytes, actionTable.Pos)
@@ -613,6 +613,7 @@
 			Harmony:          stat.Harmony,
 			RobotDied:        stat.RobotDied,
 			CollectedBy:      stat.CollectedBy,
+			CompType:         stat.CompType,
 		})
 	}
 
@@ -1152,7 +1153,7 @@
 		request.ActionsList(&action, i)
 
 		dbAction := db.Action{
-			PreScouting: request.PreScouting(),
+			CompType:    string(request.CompType()),
 			TeamNumber:  string(request.TeamNumber()),
 			MatchNumber: request.MatchNumber(),
 			SetNumber:   request.SetNumber(),
@@ -1196,77 +1197,6 @@
 	w.Write(builder.FinishedBytes())
 }
 
-type submitActionsHandler struct {
-	db Database
-}
-
-func (handler submitActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	// Get the username of the person submitting the data.
-	username := parseUsername(req)
-
-	requestBytes, err := io.ReadAll(req.Body)
-	if err != nil {
-		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
-		return
-	}
-
-	request, success := parseRequest(w, requestBytes, "SubmitActions", submit_actions.GetRootAsSubmitActions)
-	if !success {
-		return
-	}
-
-	log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "from", username)
-
-	for i := 0; i < request.ActionsListLength(); i++ {
-
-		var action Action
-		request.ActionsList(&action, i)
-
-		dbAction := db.Action{
-			PreScouting: request.PreScouting(),
-			TeamNumber:  string(request.TeamNumber()),
-			MatchNumber: request.MatchNumber(),
-			SetNumber:   request.SetNumber(),
-			CompLevel:   string(request.CompLevel()),
-			//TODO: Serialize CompletedAction
-			CompletedAction: []byte{},
-			Timestamp:       action.Timestamp(),
-			CollectedBy:     username,
-		}
-
-		// Do some error checking.
-		if action.Timestamp() < 0 {
-			respondWithError(w, http.StatusBadRequest, fmt.Sprint(
-				"Invalid timestamp field value of ", action.Timestamp()))
-			return
-		}
-
-		err = handler.db.AddAction(dbAction)
-		if err != nil {
-			respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
-			return
-		}
-	}
-
-	stats, err := ConvertActionsToStat(request)
-	if err != nil {
-		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
-		return
-	}
-
-	stats.CollectedBy = username
-
-	err = handler.db.AddToStats2023(stats)
-	if err != nil {
-		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats: ", stats, ": ", err))
-		return
-	}
-
-	builder := flatbuffers.NewBuilder(50 * 1024)
-	builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
-	w.Write(builder.FinishedBytes())
-}
-
 type Delete2024DataScoutingHandler struct {
 	db Database
 }
@@ -1370,7 +1300,6 @@
 	scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
 	scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
 	scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
-	scoutingServer.Handle("/requests/submit/submit_actions", submitActionsHandler{db})
 	scoutingServer.Handle("/requests/submit/submit_2024_actions", submit2024ActionsHandler{db})
 	scoutingServer.Handle("/requests/delete/delete_2023_data_scouting", Delete2023DataScoutingHandler{db})
 	scoutingServer.Handle("/requests/delete/delete_2024_data_scouting", Delete2024DataScoutingHandler{db})
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index fae542c..f8fb2ca 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -7,7 +7,6 @@
 
 	"github.com/frc971/971-Robot-Code/scouting/db"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/debug"
-	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2023_data_scouting"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2024_data_scouting"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
@@ -133,7 +132,7 @@
 		// Pretend that we have some data scouting data.
 		stats2024: []db.Stats2024{
 			{
-				PreScouting: false, TeamNumber: "5",
+				CompType: "Regular", TeamNumber: "5",
 				MatchNumber: 1, SetNumber: 1, CompLevel: "qm", StartingQuadrant: 3,
 				SpeakerAuto: 2, AmpAuto: 4, NotesDroppedAuto: 1, MobilityAuto: true,
 				Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 1, OutOfField: 2,
@@ -141,7 +140,7 @@
 				Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "alex",
 			},
 			{
-				PreScouting: false, TeamNumber: "973",
+				CompType: "Regular", TeamNumber: "973",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "qm", StartingQuadrant: 1,
 				SpeakerAuto: 0, AmpAuto: 2, NotesDroppedAuto: 0, MobilityAuto: false,
 				Speaker: 0, Amp: 4, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
@@ -210,7 +209,7 @@
 	db := MockDatabase{
 		stats2024: []db.Stats2024{
 			{
-				PreScouting: false, TeamNumber: "342",
+				CompType: "Regular", TeamNumber: "342",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 				SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
 				Speaker: 4, Amp: 2, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
@@ -218,7 +217,7 @@
 				Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
 			},
 			{
-				PreScouting: false, TeamNumber: "982",
+				CompType: "Regular", TeamNumber: "982",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 				SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 				Speaker: 0, Amp: 2, SpeakerAmplified: 3, Shuttled: 1, OutOfField: 0,
@@ -243,7 +242,7 @@
 	expected := request_2024_data_scouting_response.Request2024DataScoutingResponseT{
 		StatsList: []*request_2024_data_scouting_response.Stats2024T{
 			{
-				PreScouting: false, TeamNumber: "342",
+				CompType: "Regular", TeamNumber: "342",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 4,
 				SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
 				Speaker: 4, Amp: 2, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
@@ -251,7 +250,7 @@
 				Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
 			},
 			{
-				PreScouting: false, TeamNumber: "982",
+				CompType: "Regular", TeamNumber: "982",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 				SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 				Speaker: 0, Amp: 2, SpeakerAmplified: 3, Shuttled: 1, OutOfField: 0,
@@ -493,7 +492,7 @@
 				Timestamp: 4200,
 			},
 		},
-		PreScouting: false,
+		CompType: "Regular",
 	}).Pack(builder))
 
 	submit2024Actions := submit_2024_actions.GetRootAsSubmit2024Actions(builder.FinishedBytes(), 0)
@@ -504,7 +503,7 @@
 	}
 
 	expected := db.Stats2024{
-		PreScouting: false, TeamNumber: "4244",
+		CompType: "Regular", TeamNumber: "4244",
 		MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 		SpeakerAuto: 0, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
 		Speaker: 0, Amp: 0, SpeakerAmplified: 1, Shuttled: 1, OutOfField: 0,
@@ -517,158 +516,6 @@
 	}
 }
 
-// Validates that we can request the 2023 stats.
-func TestConvertActionsToStat(t *testing.T) {
-	builder := flatbuffers.NewBuilder(1024)
-	builder.Finish((&submit_actions.SubmitActionsT{
-		TeamNumber:  "4244",
-		MatchNumber: 3,
-		SetNumber:   1,
-		CompLevel:   "quals",
-		ActionsList: []*submit_actions.ActionT{
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypeStartMatchAction,
-					Value: &submit_actions.StartMatchActionT{
-						Position: 1,
-					},
-				},
-				Timestamp: 0,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePickupObjectAction,
-					Value: &submit_actions.PickupObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						Auto:       true,
-					},
-				},
-				Timestamp: 400,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePickupObjectAction,
-					Value: &submit_actions.PickupObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						Auto:       true,
-					},
-				},
-				Timestamp: 800,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePlaceObjectAction,
-					Value: &submit_actions.PlaceObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						ScoreLevel: submit_actions.ScoreLevelkLow,
-						Auto:       true,
-					},
-				},
-				Timestamp: 2000,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypeMobilityAction,
-					Value: &submit_actions.MobilityActionT{
-						Mobility: true,
-					},
-				},
-				Timestamp: 2200,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypeAutoBalanceAction,
-					Value: &submit_actions.AutoBalanceActionT{
-						Docked:         true,
-						Engaged:        true,
-						BalanceAttempt: false,
-					},
-				},
-				Timestamp: 2400,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePickupObjectAction,
-					Value: &submit_actions.PickupObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCone,
-						Auto:       false,
-					},
-				},
-				Timestamp: 2800,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePlaceObjectAction,
-					Value: &submit_actions.PlaceObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCone,
-						ScoreLevel: submit_actions.ScoreLevelkHigh,
-						Auto:       false,
-					},
-				},
-				Timestamp: 3100,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePickupObjectAction,
-					Value: &submit_actions.PickupObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						Auto:       false,
-					},
-				},
-				Timestamp: 3500,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePlaceObjectAction,
-					Value: &submit_actions.PlaceObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						ScoreLevel: submit_actions.ScoreLevelkSupercharged,
-						Auto:       false,
-					},
-				},
-				Timestamp: 3900,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypeEndMatchAction,
-					Value: &submit_actions.EndMatchActionT{
-						Docked:         true,
-						Engaged:        false,
-						BalanceAttempt: true,
-					},
-				},
-				Timestamp: 4200,
-			},
-		},
-		PreScouting: false,
-	}).Pack(builder))
-
-	submitActions := submit_actions.GetRootAsSubmitActions(builder.FinishedBytes(), 0)
-	response, err := ConvertActionsToStat(submitActions)
-
-	if err != nil {
-		t.Fatal("Failed to convert actions to stats: ", err)
-	}
-
-	expected := db.Stats2023{
-		PreScouting: false,
-		TeamNumber:  "4244", MatchNumber: 3, SetNumber: 1,
-		CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 1,
-		MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 1,
-		LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
-		ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0,
-		HighCubes: 0, CubesDropped: 0, LowCones: 0,
-		MiddleCones: 0, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 1,
-		AvgCycle: 950, Mobility: true, DockedAuto: true, EngagedAuto: true,
-		BalanceAttemptAuto: false, Docked: true, Engaged: false,
-		BalanceAttempt: true, CollectedBy: "",
-	}
-
-	if expected != response {
-		t.Fatal("Expected ", expected, ", but got ", response)
-	}
-}
-
 func TestSubmitNotes(t *testing.T) {
 	database := MockDatabase{}
 	scoutingServer := server.NewScoutingServer()
@@ -1220,7 +1067,7 @@
 				Timestamp: 2500,
 			},
 		},
-		PreScouting: true,
+		CompType: "Prescouting",
 	}).Pack(builder))
 
 	_, err := debug.Submit2024Actions("http://localhost:8080", builder.FinishedBytes())
@@ -1230,7 +1077,7 @@
 
 	expectedActions := []db.Action{
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "3421",
 			MatchNumber:     2,
 			SetNumber:       1,
@@ -1240,7 +1087,7 @@
 			Timestamp:       1800,
 		},
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "3421",
 			MatchNumber:     2,
 			SetNumber:       1,
@@ -1253,7 +1100,7 @@
 
 	expectedStats := []db.Stats2024{
 		db.Stats2024{
-			PreScouting: true, TeamNumber: "3421",
+			CompType: "Prescouting", TeamNumber: "3421",
 			MatchNumber: 2, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 0,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 1, Amp: 0, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
@@ -1270,236 +1117,12 @@
 	}
 }
 
-func TestAddingActions(t *testing.T) {
-	database := MockDatabase{}
-	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&database, scoutingServer)
-	scoutingServer.Start(8080)
-	defer scoutingServer.Stop()
-
-	builder := flatbuffers.NewBuilder(1024)
-	builder.Finish((&submit_actions.SubmitActionsT{
-		TeamNumber:  "1234",
-		MatchNumber: 4,
-		SetNumber:   1,
-		CompLevel:   "qual",
-		ActionsList: []*submit_actions.ActionT{
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePickupObjectAction,
-					Value: &submit_actions.PickupObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						Auto:       true,
-					},
-				},
-				Timestamp: 2400,
-			},
-			{
-				ActionTaken: &submit_actions.ActionTypeT{
-					Type: submit_actions.ActionTypePlaceObjectAction,
-					Value: &submit_actions.PlaceObjectActionT{
-						ObjectType: submit_actions.ObjectTypekCube,
-						ScoreLevel: submit_actions.ScoreLevelkLow,
-						Auto:       false,
-					},
-				},
-				Timestamp: 1009,
-			},
-		},
-		PreScouting: true,
-	}).Pack(builder))
-
-	_, err := debug.SubmitActions("http://localhost:8080", builder.FinishedBytes())
-	if err != nil {
-		t.Fatal("Failed to submit actions: ", err)
-	}
-
-	// Make sure that the data made it into the database.
-	// TODO: Add this back when we figure out how to add the serialized action into the database.
-
-	/* expectedActionsT := []*submit_actions.ActionT{
-		{
-			ActionTaken: &submit_actions.ActionTypeT{
-				Type:	submit_actions.ActionTypePickupObjectAction,
-				Value:	&submit_actions.PickupObjectActionT{
-					ObjectType: submit_actions.ObjectTypekCube,
-					Auto: true,
-				},
-			},
-			Timestamp:       2400,
-		},
-		{
-			ActionTaken: &submit_actions.ActionTypeT{
-				Type:	submit_actions.ActionTypePlaceObjectAction,
-				Value:	&submit_actions.PlaceObjectActionT{
-					ObjectType: submit_actions.ObjectTypekCube,
-					ScoreLevel: submit_actions.ScoreLevelkLow,
-					Auto: false,
-				},
-			},
-			Timestamp:       1009,
-		},
-	} */
-
-	expectedActions := []db.Action{
-		{
-			PreScouting:     true,
-			TeamNumber:      "1234",
-			MatchNumber:     4,
-			SetNumber:       1,
-			CompLevel:       "qual",
-			CollectedBy:     "debug_cli",
-			CompletedAction: []byte{},
-			Timestamp:       2400,
-		},
-		{
-			PreScouting:     true,
-			TeamNumber:      "1234",
-			MatchNumber:     4,
-			SetNumber:       1,
-			CompLevel:       "qual",
-			CollectedBy:     "debug_cli",
-			CompletedAction: []byte{},
-			Timestamp:       1009,
-		},
-	}
-
-	expectedStats := []db.Stats2023{
-		db.Stats2023{
-			PreScouting: true,
-			TeamNumber:  "1234", MatchNumber: 4, SetNumber: 1,
-			CompLevel: "qual", StartingQuadrant: 0, LowCubesAuto: 0,
-			MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
-			LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
-			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 0,
-			HighCubes: 0, CubesDropped: 0, LowCones: 0,
-			MiddleCones: 0, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0,
-			AvgCycle: 0, Mobility: false, DockedAuto: false, EngagedAuto: false,
-			BalanceAttemptAuto: false, Docked: false, Engaged: false,
-			BalanceAttempt: false, CollectedBy: "debug_cli",
-		},
-	}
-
-	if !reflect.DeepEqual(expectedActions, database.actions) {
-		t.Fatal("Expected ", expectedActions, ", but got:", database.actions)
-	}
-	if !reflect.DeepEqual(expectedStats, database.stats2023) {
-		t.Fatal("Expected ", expectedStats, ", but got:", database.stats2023)
-	}
-}
-
-// Validates that we can delete stats.
-func TestDeleteFromStats(t *testing.T) {
-	database := MockDatabase{
-		stats2023: []db.Stats2023{
-			{
-				TeamNumber: "3634", MatchNumber: 1, SetNumber: 2,
-				CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 10,
-				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
-				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
-				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
-				HighCubes: 2, CubesDropped: 1, LowCones: 1,
-				MiddleCones: 2, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
-				AvgCycle: 34, Mobility: false, DockedAuto: true, EngagedAuto: false,
-				BalanceAttemptAuto: false, Docked: false, Engaged: false,
-				BalanceAttempt: true, CollectedBy: "isaac",
-			},
-			{
-				TeamNumber: "2343", MatchNumber: 1, SetNumber: 2,
-				CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 0,
-				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 2,
-				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
-				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
-				HighCubes: 1, CubesDropped: 0, LowCones: 0,
-				MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
-				AvgCycle: 53, Mobility: false, DockedAuto: false, EngagedAuto: false,
-				BalanceAttemptAuto: true, Docked: false, Engaged: false,
-				BalanceAttempt: true, CollectedBy: "unknown",
-			},
-		},
-		actions: []db.Action{
-			{
-				PreScouting:     true,
-				TeamNumber:      "3634",
-				MatchNumber:     1,
-				SetNumber:       2,
-				CompLevel:       "quals",
-				CollectedBy:     "debug_cli",
-				CompletedAction: []byte{},
-				Timestamp:       2400,
-			},
-			{
-				PreScouting:     true,
-				TeamNumber:      "2343",
-				MatchNumber:     1,
-				SetNumber:       2,
-				CompLevel:       "quals",
-				CollectedBy:     "debug_cli",
-				CompletedAction: []byte{},
-				Timestamp:       1009,
-			},
-		},
-	}
-	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&database, scoutingServer)
-	scoutingServer.Start(8080)
-	defer scoutingServer.Stop()
-
-	builder := flatbuffers.NewBuilder(1024)
-	builder.Finish((&delete_2023_data_scouting.Delete2023DataScoutingT{
-		CompLevel:   "quals",
-		MatchNumber: 1,
-		SetNumber:   2,
-		TeamNumber:  "2343",
-	}).Pack(builder))
-
-	_, err := debug.Delete2023DataScouting("http://localhost:8080", builder.FinishedBytes())
-	if err != nil {
-		t.Fatal("Failed to delete from data scouting ", err)
-	}
-
-	expectedActions := []db.Action{
-		{
-			PreScouting:     true,
-			TeamNumber:      "3634",
-			MatchNumber:     1,
-			SetNumber:       2,
-			CompLevel:       "quals",
-			CollectedBy:     "debug_cli",
-			CompletedAction: []byte{},
-			Timestamp:       2400,
-		},
-	}
-
-	expectedStats := []db.Stats2023{
-		{
-			TeamNumber: "3634", MatchNumber: 1, SetNumber: 2,
-			CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 10,
-			MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
-			LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
-			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
-			HighCubes: 2, CubesDropped: 1, LowCones: 1,
-			MiddleCones: 2, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
-			AvgCycle: 34, Mobility: false, DockedAuto: true, EngagedAuto: false,
-			BalanceAttemptAuto: false, Docked: false, Engaged: false,
-			BalanceAttempt: true, CollectedBy: "isaac",
-		},
-	}
-
-	if !reflect.DeepEqual(expectedActions, database.actions) {
-		t.Fatal("Expected ", expectedActions, ", but got:", database.actions)
-	}
-	if !reflect.DeepEqual(expectedStats, database.stats2023) {
-		t.Fatal("Expected ", expectedStats, ", but got:", database.stats2023)
-	}
-}
-
 // Validates that we can delete 2024 stats.
 func TestDeleteFromStats2024(t *testing.T) {
 	database := MockDatabase{
 		stats2024: []db.Stats2024{
 			{
-				PreScouting: false, TeamNumber: "746",
+				CompType: "Practice", TeamNumber: "746",
 				MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
 				SpeakerAuto: 0, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
 				Speaker: 0, Amp: 1, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
@@ -1507,7 +1130,7 @@
 				Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "alek",
 			},
 			{
-				PreScouting: false, TeamNumber: "244",
+				CompType: "Regular", TeamNumber: "244",
 				MatchNumber: 5, SetNumber: 3, CompLevel: "quals", StartingQuadrant: 1,
 				SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 				Speaker: 0, Amp: 0, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
@@ -1517,7 +1140,7 @@
 		},
 		actions: []db.Action{
 			{
-				PreScouting:     true,
+				CompType:        "Practice",
 				TeamNumber:      "746",
 				MatchNumber:     3,
 				SetNumber:       1,
@@ -1527,7 +1150,7 @@
 				Timestamp:       2400,
 			},
 			{
-				PreScouting:     true,
+				CompType:        "Prescouting",
 				TeamNumber:      "244",
 				MatchNumber:     5,
 				SetNumber:       3,
@@ -1558,7 +1181,7 @@
 
 	expectedActions := []db.Action{
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "244",
 			MatchNumber:     5,
 			SetNumber:       3,
@@ -1571,7 +1194,7 @@
 
 	expectedStats := []db.Stats2024{
 		{
-			PreScouting: false, TeamNumber: "244",
+			CompType: "Regular", TeamNumber: "244",
 			MatchNumber: 5, SetNumber: 3, CompLevel: "quals", StartingQuadrant: 1,
 			SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
 			Speaker: 0, Amp: 0, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
@@ -1638,10 +1261,10 @@
 	return results, nil
 }
 
-func (database *MockDatabase) ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2024, error) {
+func (database *MockDatabase) ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, compType string) ([]db.Stats2024, error) {
 	var results []db.Stats2024
 	for _, stats := range database.stats2024 {
-		if stats.TeamNumber == teamNumber && stats.MatchNumber == matchNumber && stats.SetNumber == setNumber && stats.CompLevel == compLevel && stats.PreScouting == preScouting {
+		if stats.TeamNumber == teamNumber && stats.MatchNumber == matchNumber && stats.SetNumber == setNumber && stats.CompLevel == compLevel && stats.CompType == compType {
 			results = append(results, stats)
 		}
 	}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 41f492c..98c9311 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -51,6 +51,8 @@
   | 'QR Code'
   | 'Success';
 
+type CompType = 'PreScouting' | 'Practice' | 'Regular';
+
 // TODO(phil): Deduplicate with match_list.component.ts.
 const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
 export type CompLevel = typeof COMP_LEVELS[number];
@@ -106,6 +108,7 @@
   @Input() teamNumber: string = '1';
   @Input() setNumber: number = 1;
   @Input() compLevel: CompLevel = 'qm';
+  @Input() compType: CompType = 'Regular';
   @Input() skipTeamSelection = false;
 
   @ViewChild('header') header: ElementRef;
@@ -126,7 +129,6 @@
 
   nextTeamNumber = '';
 
-  preScouting: boolean = false;
   matchStartTimestamp: number = 0;
   penalties: number = 0;
 
@@ -181,9 +183,10 @@
 
   // This gets called when the user changes something on the Init screen.
   // It makes sure that the user can't click "Next" until the information is
-  // valid, or this is for pre-scouting.
+  // valid, or this is for pre-scouting or practice matches.
   updateTeamSelectionValidity(): void {
-    this.teamSelectionIsValid = this.preScouting || this.matchIsInMatchList();
+    this.teamSelectionIsValid =
+      this.compType != 'Regular' || this.matchIsInMatchList();
   }
 
   matchIsInMatchList(): boolean {
@@ -323,6 +326,7 @@
     }
     const teamNumberFb = builder.createString(this.teamNumber);
     const compLevelFb = builder.createString(this.compLevel);
+    const compTypeFb = builder.createString(this.compType);
 
     const actionsVector = Submit2024Actions.createActionsListVector(
       builder,
@@ -334,7 +338,7 @@
     Submit2024Actions.addSetNumber(builder, this.setNumber);
     Submit2024Actions.addCompLevel(builder, compLevelFb);
     Submit2024Actions.addActionsList(builder, actionsVector);
-    Submit2024Actions.addPreScouting(builder, this.preScouting);
+    Submit2024Actions.addCompType(builder, compTypeFb);
     builder.finish(Submit2024Actions.endSubmit2024Actions(builder));
 
     return builder.asUint8Array();
@@ -442,7 +446,7 @@
       this.autoPhase = true;
       this.actionList = [];
       this.mobilityCompleted = false;
-      this.preScouting = false;
+      this.compType = 'Regular';
       this.matchStartTimestamp = 0;
       this.selectedValue = 0;
     } else {
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index a5d778c..3a672e1 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -58,13 +58,28 @@
       <label>
         <input
           id="pre_scouting"
-          type="checkbox"
-          [(ngModel)]="preScouting"
+          name="comp_type"
+          type="radio"
+          [(ngModel)]="compType"
+          value="Prescouting"
           (ngModelChange)="updateTeamSelectionValidity()"
         />
         Pre-scouting
       </label>
     </div>
+    <div class="row">
+      <label>
+        <input
+          id="practice_match"
+          name="comp_type"
+          type="radio"
+          [(ngModel)]="compType"
+          value="Practice"
+          (ngModelChange)="updateTeamSelectionValidity()"
+        />
+        Practice Match
+      </label>
+    </div>
     <div class="buttons">
       <!-- hack to right align the next button -->
       <div></div>
@@ -429,6 +444,7 @@
   <div *ngSwitchCase="'Review and Submit'" id="Review" class="container-fluid">
     <div class="row">
       <ul id="review_data">
+        <div *ngIf="compType!='Regular'">This is a {{this.compType}} match</div>
         <li *ngFor="let action of actionList" style="display: flex">
           <div [ngSwitch]="action.actionTakenType" style="padding: 0px">
             <span *ngSwitchCase="ActionType.StartMatchAction">