Merge "Sandbox libxml2 and switch clang to zstd compression"
diff --git a/scouting/db/db.go b/scouting/db/db.go
index b2feabe..9df6097 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -69,25 +69,23 @@
 }
 
 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"`
-	StartingQuadrant                             int32
-	SpeakerAuto, AmpAuto                         int32
-	NotesDroppedAuto                             int32
-	MobilityAuto                                 bool
-	Speaker, Amp, SpeakerAmplified, AmpAmplified int32
-	NotesDropped                                 int32
-	Penalties                                    int32
-	AvgCycle                                     int64
-	RobotDied                                    bool
-	Park, OnStage, Harmony, TrapNote, Spotlight  bool
+	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
+	MobilityAuto                                bool
+	Speaker, Amp, SpeakerAmplified              int32
+	NotesDropped                                int32
+	Shuttled, OutOfField                        int32
+	Penalties                                   int32
+	AvgCycle                                    int64
+	RobotDied                                   bool
+	Park, OnStage, Harmony, TrapNote, Spotlight bool
+	NoShow                                      bool
 
 	// The username of the person who collected these statistics.
 	// "unknown" if submitted without logging in.
@@ -96,11 +94,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"`
@@ -242,7 +240,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
@@ -359,11 +357,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 c3b1143..ed71880 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -144,52 +144,60 @@
 
 	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, AmpAmplified: 0,
+			Speaker: 0, Amp: 5, SpeakerAmplified: 1,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 0,
+			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: true, 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, AmpAmplified: 0,
+			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: "henry",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "kaleb",
 		},
 		Stats2024{
-			PreScouting: false, TeamNumber: "52A",
+			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,
+			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "henry",
+		},
+		Stats2024{
+			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, AmpAmplified: 3,
+			Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 2, Penalties: 0, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "jordan",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 1,
+			Speaker: 5, Amp: 0, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 1, Penalties: 1, TrapNote: true, Spotlight: true, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "taylor",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 2,
+			Speaker: 0, Amp: 3, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 3, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "katie",
+			Park: false, OnStage: false, Harmony: true, RobotDied: false, NoShow: true, CollectedBy: "katie",
 		},
 	}
 
@@ -231,12 +239,12 @@
 	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, AmpAmplified: 0,
+		Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 1,
 		NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-		Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
+		Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "emma",
 	}
 
 	// Attempt to insert the non-pre-scouted data and make sure it fails.
@@ -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,28 +296,28 @@
 
 	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, AmpAmplified: 0,
+			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 2, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "emma",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 2,
+			Speaker: 1, Amp: 2, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: true, 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, AmpAmplified: 5,
+			Speaker: 0, Amp: 1, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 1,
 			NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
-			Park: false, OnStage: false, Harmony: true, RobotDied: true, CollectedBy: "emma",
+			Park: false, OnStage: false, Harmony: true, RobotDied: true, NoShow: false, CollectedBy: "emma",
 		},
 	}
 
@@ -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,47 +731,47 @@
 
 	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, AmpAmplified: 3,
+			Speaker: 1, Amp: 3, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 3,
 			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "bailey",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: true, 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, AmpAmplified: 1,
+			Speaker: 1, Amp: 2, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "kate",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 1,
+			Speaker: 0, Amp: 0, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 2,
 			NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "tyler",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 1,
+			Speaker: 1, Amp: 2, SpeakerAmplified: 2, Shuttled: 2, OutOfField: 2,
 			NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "max",
+			Park: false, OnStage: false, Harmony: true, RobotDied: false, NoShow: false, CollectedBy: "max",
 		},
 	}
 
 	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, AmpAmplified: 3,
+			Speaker: 1, Amp: 3, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 3,
 			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "bailey",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: true, CollectedBy: "bailey",
 		},
 	}
 
@@ -1149,36 +1185,36 @@
 
 	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, AmpAmplified: 0,
+			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 0,
+			Speaker: 0, Amp: 5, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 0,
+			Speaker: 2, Amp: 1, SpeakerAmplified: 3, Shuttled: 5, OutOfField: 1,
 			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "henry",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 3,
+			Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 2, Penalties: 0, TrapNote: true, Spotlight: true, AvgCycle: 0,
-			Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "jordan",
+			Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "jordan",
 		},
 	}
 
diff --git a/scouting/scouting_qrcode_test.cy.js b/scouting/scouting_qrcode_test.cy.js
index 559481e..34da4da 100644
--- a/scouting/scouting_qrcode_test.cy.js
+++ b/scouting/scouting_qrcode_test.cy.js
@@ -64,7 +64,7 @@
   // Pick and Place Cube in Teleop.
   clickButton('Start Teleop');
   clickButton('NOTE');
-  clickButton('AMP AMPLIFIED');
+  clickButton('SPEAKER AMPLIFIED');
 
   // Generate some extra actions so that we are guaranteed to have at least 2
   // QR codes.
diff --git a/scouting/scouting_test.cy.js b/scouting/scouting_test.cy.js
index 7600bb5..2727104 100644
--- a/scouting/scouting_test.cy.js
+++ b/scouting/scouting_test.cy.js
@@ -91,7 +91,7 @@
   // Pick and Place Cube in Teleop.
   clickButton('Start Teleop');
   clickButton('NOTE');
-  clickButton('AMP AMPLIFIED');
+  clickButton('SPEAKER AMPLIFIED');
 
   // Robot dead and revive.
   clickButton('DEAD');
@@ -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 f450d38..c0ac039 100644
--- a/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
@@ -15,8 +15,10 @@
   speaker:int (id:7);
   amp:int (id:8);
   speaker_amplified:int (id:9);
-  amp_amplified:int (id:10);
+  amp_amplified:int (id:10, deprecated);
   notes_dropped:int (id:11);
+  shuttled:int (id:24);
+  out_of_field:int (id:25);
 
   penalties:int (id:12);
   trap_note:bool (id:13);
@@ -27,9 +29,11 @@
   harmony: bool (id:17);
   spotlight: bool (id:22);
   robot_died: bool (id:23);
+  no_show: bool (id:27);
 
-  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 e927b67..fce9045 100644
--- a/scouting/webserver/requests/messages/submit_2024_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_2024_actions.fbs
@@ -6,12 +6,15 @@
 
 enum ScoreType: short {
     kAMP,
-    kAMP_AMPLIFIED,
     kSPEAKER,
     kSPEAKER_AMPLIFIED,
     kDROPPED,
+    kOUT_OF_FIELD,
+    kSHUTTLED,
 }
 
+table NoShowAction {}
+
 table MobilityAction {
     mobility:bool (id:0);
 }
@@ -54,6 +57,7 @@
 
 union ActionType {
     MobilityAction,
+    NoShowAction,
     StartMatchAction,
     EndAutoPhaseAction,
     PickupNoteAction,
@@ -76,8 +80,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 1536093..0367ff2 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,10 +453,11 @@
 	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, AmpAmplified: 0, NotesDropped: 0, Penalties: 0,
-		TrapNote: false, Spotlight: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "",
+		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, NoShow: false, CollectedBy: "",
 	}
 	// Loop over all actions.
 	for i := 0; i < submit2024Actions.ActionsListLength(); i++ {
@@ -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)
@@ -490,6 +490,11 @@
 			robotDeathAction.Init(actionTable.Bytes, actionTable.Pos)
 			stat.RobotDied = true
 
+		} else if action_type == submit_2024_actions.ActionTypeNoShowAction {
+			var NoShowAction submit_2024_actions.NoShowAction
+			NoShowAction.Init(actionTable.Bytes, actionTable.Pos)
+			stat.NoShow = true
+
 		} else if action_type == submit_2024_actions.ActionTypePickupNoteAction {
 			var pick_up_action submit_2024_actions.PickupNoteAction
 			pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
@@ -502,12 +507,11 @@
 			}
 			score_type := place_action.ScoreType()
 			auto := place_action.Auto()
+			count_in_cycle := true
 			if score_type == submit_2024_actions.ScoreTypekAMP && auto {
 				stat.AmpAuto += 1
 			} else if score_type == submit_2024_actions.ScoreTypekAMP && !auto {
 				stat.Amp += 1
-			} else if score_type == submit_2024_actions.ScoreTypekAMP_AMPLIFIED && !auto {
-				stat.AmpAmplified += 1
 			} else if score_type == submit_2024_actions.ScoreTypekSPEAKER && !auto {
 				stat.Speaker += 1
 			} else if score_type == submit_2024_actions.ScoreTypekSPEAKER && auto {
@@ -516,20 +520,32 @@
 				stat.SpeakerAmplified += 1
 			} else if score_type == submit_2024_actions.ScoreTypekDROPPED && auto {
 				stat.NotesDroppedAuto += 1
+				count_in_cycle = false
 			} else if score_type == submit_2024_actions.ScoreTypekDROPPED && !auto {
 				stat.NotesDropped += 1
+				count_in_cycle = false
+			} else if score_type == submit_2024_actions.ScoreTypekSHUTTLED {
+				stat.Shuttled += 1
+				count_in_cycle = false
+			} else if score_type == submit_2024_actions.ScoreTypekOUT_OF_FIELD {
+				stat.OutOfField += 1
+				count_in_cycle = false
 			} else {
 				return db.Stats2024{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
 			}
 			picked_up = false
-			if lastPlacedTime != int64(0) {
-				// If this is not the first time we place,
-				// start counting cycle time. We define cycle
-				// time as the time between placements.
-				overall_time += int64(action.Timestamp()) - lastPlacedTime
+			if count_in_cycle {
+				// Assuming dropped, shuttled, and out of field
+				// notes are not counted in total cycle time.
+				if lastPlacedTime != int64(0) {
+					// If this is not the first time we place,
+					// start counting cycle time. We define cycle
+					// time as the time between placements.
+					overall_time += int64(action.Timestamp()) - lastPlacedTime
+				}
 				cycles += 1
+				lastPlacedTime = int64(action.Timestamp())
 			}
-			lastPlacedTime = int64(action.Timestamp())
 		} else if action_type == submit_2024_actions.ActionTypeEndMatchAction {
 			var endMatchAction submit_2024_actions.EndMatchAction
 			endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
@@ -590,8 +606,9 @@
 			Speaker:          stat.Speaker,
 			Amp:              stat.Amp,
 			SpeakerAmplified: stat.SpeakerAmplified,
-			AmpAmplified:     stat.AmpAmplified,
 			NotesDropped:     stat.NotesDropped,
+			Shuttled:         stat.Shuttled,
+			OutOfField:       stat.OutOfField,
 			Penalties:        stat.Penalties,
 			TrapNote:         stat.TrapNote,
 			Spotlight:        stat.Spotlight,
@@ -600,7 +617,9 @@
 			OnStage:          stat.OnStage,
 			Harmony:          stat.Harmony,
 			RobotDied:        stat.RobotDied,
+			NoShow:           stat.NoShow,
 			CollectedBy:      stat.CollectedBy,
+			CompType:         stat.CompType,
 		})
 	}
 
@@ -1123,16 +1142,18 @@
 
 	requestBytes, err := io.ReadAll(req.Body)
 	if err != nil {
+		log.Println("Failed to receive submission request from", username)
 		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
 		return
 	}
 
 	request, success := parseRequest(w, requestBytes, "Submit2024Actions", submit_2024_actions.GetRootAsSubmit2024Actions)
 	if !success {
+		log.Println("Failed to parse submission request from", username)
 		return
 	}
 
-	log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "from", username)
+	log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "type", string(request.CompType()), "from", username)
 
 	for i := 0; i < request.ActionsListLength(); i++ {
 
@@ -1140,7 +1161,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(),
@@ -1153,6 +1174,7 @@
 
 		// Do some error checking.
 		if action.Timestamp() < 0 {
+			log.Println("Got action with invalid timestamp (", action.Timestamp(), ") from", username)
 			respondWithError(w, http.StatusBadRequest, fmt.Sprint(
 				"Invalid timestamp field value of ", action.Timestamp()))
 			return
@@ -1160,6 +1182,7 @@
 
 		err = handler.db.AddAction(dbAction)
 		if err != nil {
+			log.Println("Failed to add action from", username, "to the database:", err)
 			respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
 			return
 		}
@@ -1167,6 +1190,7 @@
 
 	stats, err := ConvertActionsToStat2024(request)
 	if err != nil {
+		log.Println("Failed to add action from", username, "to the database:", err)
 		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
 		return
 	}
@@ -1175,6 +1199,7 @@
 
 	err = handler.db.AddToStats2024(stats)
 	if err != nil {
+		log.Println("Failed to submit stats from", username, "to the database:", err)
 		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats2024: ", stats, ": ", err))
 		return
 	}
@@ -1182,77 +1207,8 @@
 	builder := flatbuffers.NewBuilder(50 * 1024)
 	builder.Finish((&Submit2024ActionsResponseT{}).Pack(builder))
 	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())
+	log.Println("Successfully added stats from", username)
 }
 
 type Delete2024DataScoutingHandler struct {
@@ -1358,7 +1314,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 6968346..d0e17b8 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,20 +132,20 @@
 		// 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, AmpAmplified: 1,
+				Speaker: 0, Amp: 1, SpeakerAmplified: 2, Shuttled: 1, OutOfField: 2,
 				NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 233,
-				Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "alex",
+				Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: 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, AmpAmplified: 1,
+				Speaker: 0, Amp: 4, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
 				NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 120,
-				Park: true, OnStage: false, Harmony: false, RobotDied: true, CollectedBy: "bob",
+				Park: true, OnStage: false, Harmony: false, RobotDied: true, NoShow: false, CollectedBy: "bob",
 			},
 		},
 	}
@@ -210,20 +209,28 @@
 	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, AmpAmplified: 0,
+				Speaker: 4, Amp: 2, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
 				NotesDropped: 2, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-				Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
+				Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "alex",
 			},
 			{
-				PreScouting: false, TeamNumber: "982",
+				CompType: "Regular", TeamNumber: "132",
+				MatchNumber: 4, SetNumber: 2, CompLevel: "quals", StartingQuadrant: 0,
+				SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
+				Speaker: 0, Amp: 0, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 1,
+				NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
+				Park: false, OnStage: false, Harmony: false, RobotDied: false, NoShow: true, CollectedBy: "jeff",
+			},
+			{
+				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, AmpAmplified: 2,
+				Speaker: 0, Amp: 2, SpeakerAmplified: 3, Shuttled: 1, OutOfField: 0,
 				NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
-				Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "george",
+				Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "george",
 			},
 		},
 	}
@@ -243,20 +250,28 @@
 	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, AmpAmplified: 0,
+				Speaker: 4, Amp: 2, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
 				NotesDropped: 2, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
-				Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
+				Park: true, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "alex",
 			},
 			{
-				PreScouting: false, TeamNumber: "982",
+				CompType: "Regular", TeamNumber: "132",
+				MatchNumber: 4, SetNumber: 2, CompLevel: "quals", StartingQuadrant: 0,
+				SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
+				Speaker: 0, Amp: 0, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 1,
+				NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
+				Park: false, OnStage: false, Harmony: false, RobotDied: false, NoShow: true, CollectedBy: "jeff",
+			},
+			{
+				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, AmpAmplified: 2,
+				Speaker: 0, Amp: 2, SpeakerAmplified: 3, Shuttled: 1, OutOfField: 0,
 				NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
-				Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "george",
+				Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "george",
 			},
 		},
 	}
@@ -371,12 +386,10 @@
 			},
 			{
 				ActionTaken: &submit_2024_actions.ActionTypeT{
-					Type: submit_2024_actions.ActionTypePickupNoteAction,
-					Value: &submit_2024_actions.PickupNoteActionT{
-						Auto: true,
-					},
+					Type:  submit_2024_actions.ActionTypeNoShowAction,
+					Value: &submit_2024_actions.NoShowActionT{},
 				},
-				Timestamp: 400,
+				Timestamp: 200,
 			},
 			{
 				ActionTaken: &submit_2024_actions.ActionTypeT{
@@ -428,7 +441,7 @@
 				ActionTaken: &submit_2024_actions.ActionTypeT{
 					Type: submit_2024_actions.ActionTypePlaceNoteAction,
 					Value: &submit_2024_actions.PlaceNoteActionT{
-						ScoreType: submit_2024_actions.ScoreTypekAMP_AMPLIFIED,
+						ScoreType: submit_2024_actions.ScoreTypekSHUTTLED,
 						Auto:      false,
 					},
 				},
@@ -502,7 +515,7 @@
 				Timestamp: 4200,
 			},
 		},
-		PreScouting: false,
+		CompType: "Regular",
 	}).Pack(builder))
 
 	submit2024Actions := submit_2024_actions.GetRootAsSubmit2024Actions(builder.FinishedBytes(), 0)
@@ -513,164 +526,12 @@
 	}
 
 	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, AmpAmplified: 1,
-		NotesDropped: 1, Penalties: 5, TrapNote: false, Spotlight: false, AvgCycle: 633,
-		Park: false, OnStage: false, Harmony: true, RobotDied: true, CollectedBy: "",
-	}
-
-	if expected != response {
-		t.Fatal("Expected ", expected, ", but got ", response)
-	}
-}
-
-// 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: "",
+		Speaker: 0, Amp: 0, SpeakerAmplified: 1, Shuttled: 1, OutOfField: 0,
+		NotesDropped: 1, Penalties: 5, TrapNote: false, Spotlight: false, AvgCycle: 950,
+		Park: false, OnStage: false, Harmony: true, RobotDied: true, NoShow: true, CollectedBy: "",
 	}
 
 	if expected != response {
@@ -1229,7 +1090,7 @@
 				Timestamp: 2500,
 			},
 		},
-		PreScouting: true,
+		CompType: "Prescouting",
 	}).Pack(builder))
 
 	_, err := debug.Submit2024Actions("http://localhost:8080", builder.FinishedBytes())
@@ -1239,7 +1100,7 @@
 
 	expectedActions := []db.Action{
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "3421",
 			MatchNumber:     2,
 			SetNumber:       1,
@@ -1249,7 +1110,7 @@
 			Timestamp:       1800,
 		},
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "3421",
 			MatchNumber:     2,
 			SetNumber:       1,
@@ -1262,12 +1123,12 @@
 
 	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, AmpAmplified: 0,
+			Speaker: 1, Amp: 0, SpeakerAmplified: 0, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
-			Park: false, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "debug_cli",
+			Park: false, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "debug_cli",
 		},
 	}
 
@@ -1279,254 +1140,30 @@
 	}
 }
 
-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, AmpAmplified: 1,
+				Speaker: 0, Amp: 1, SpeakerAmplified: 1, Shuttled: 0, OutOfField: 2,
 				NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 233,
-				Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "alek",
+				Park: false, OnStage: false, Harmony: true, RobotDied: false, NoShow: 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, AmpAmplified: 1,
+				Speaker: 0, Amp: 0, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
 				NotesDropped: 0, Penalties: 1, TrapNote: false, Spotlight: false, AvgCycle: 120,
-				Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "kacey",
+				Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "kacey",
 			},
 		},
 		actions: []db.Action{
 			{
-				PreScouting:     true,
+				CompType:        "Practice",
 				TeamNumber:      "746",
 				MatchNumber:     3,
 				SetNumber:       1,
@@ -1536,7 +1173,7 @@
 				Timestamp:       2400,
 			},
 			{
-				PreScouting:     true,
+				CompType:        "Prescouting",
 				TeamNumber:      "244",
 				MatchNumber:     5,
 				SetNumber:       3,
@@ -1567,7 +1204,7 @@
 
 	expectedActions := []db.Action{
 		{
-			PreScouting:     true,
+			CompType:        "Prescouting",
 			TeamNumber:      "244",
 			MatchNumber:     5,
 			SetNumber:       3,
@@ -1580,12 +1217,12 @@
 
 	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, AmpAmplified: 1,
+			Speaker: 0, Amp: 0, SpeakerAmplified: 3, Shuttled: 0, OutOfField: 0,
 			NotesDropped: 0, Penalties: 1, TrapNote: false, Spotlight: false, AvgCycle: 120,
-			Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "kacey",
+			Park: false, OnStage: true, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "kacey",
 		},
 	}
 
@@ -1647,10 +1284,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/BUILD b/scouting/www/entry/BUILD
index 24da904..85af3ca 100644
--- a/scouting/www/entry/BUILD
+++ b/scouting/www/entry/BUILD
@@ -24,6 +24,7 @@
         # nice not to have a duplicate list here when they're already known in
         # the .fbs file.
         "ACTIONS": [
+            "NoShowAction",
             "EndMatchAction",
             "MobilityAction",
             "PenaltyAction",
diff --git a/scouting/www/entry/entry.component.css b/scouting/www/entry/entry.component.css
index e646a25..c58a94f 100644
--- a/scouting/www/entry/entry.component.css
+++ b/scouting/www/entry/entry.component.css
@@ -68,3 +68,18 @@
 .qrcode {
   padding: 0px;
 }
+
+.half-button {
+  width: 46%;
+  height: 10vh;
+  margin: 0px 10px 10px 0px;
+}
+
+.half-button-container {
+  display: flex-wrap;
+  padding: 0;
+  justify-content: center;
+  text-align: center;
+  align-content: center;
+  margin: 0;
+}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 41f492c..230bfec 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -13,6 +13,8 @@
 import {
   StartMatchAction,
   StartMatchActionT,
+  NoShowActionT,
+  NoShowAction,
   ScoreType,
   StageType,
   Submit2024Actions,
@@ -51,6 +53,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];
@@ -69,7 +73,7 @@
 const QR_CODE_PIECE_SIZES = [150, 300, 450, 600, 750, 900];
 
 // The default index into QR_CODE_PIECE_SIZES.
-const DEFAULT_QR_CODE_PIECE_SIZE_INDEX = QR_CODE_PIECE_SIZES.indexOf(750);
+const DEFAULT_QR_CODE_PIECE_SIZE_INDEX = QR_CODE_PIECE_SIZES.indexOf(450);
 
 // The actions that are purely used for tracking state. They don't actually
 // have any permanent meaning and will not be saved in the database.
@@ -93,6 +97,7 @@
   readonly StageType = StageType;
   readonly ActionT = ActionT;
   readonly ActionType = ActionType;
+  readonly NoShowActionT = NoShowActionT;
   readonly StartMatchActionT = StartMatchActionT;
   readonly MobilityActionT = MobilityActionT;
   readonly PickupNoteActionT = PickupNoteActionT;
@@ -106,6 +111,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 +132,6 @@
 
   nextTeamNumber = '';
 
-  preScouting: boolean = false;
   matchStartTimestamp: number = 0;
   penalties: number = 0;
 
@@ -181,9 +186,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 {
@@ -256,6 +262,11 @@
         case ActionType.EndAutoPhaseAction:
           this.autoPhase = true;
           this.section = 'Pickup';
+          break;
+        case ActionType.NoShowAction:
+          this.autoPhase = true;
+          this.section = 'Init';
+          break;
         case ActionType.PickupNoteAction:
           this.section = 'Pickup';
           break;
@@ -323,6 +334,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 +346,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 +454,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 d95bcec..1b3a966 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>
@@ -100,6 +115,12 @@
       <!-- Creates a responsive stack of full-width, "block buttons". -->
       <div class="d-grid gap-5">
         <button
+          class="btn btn-warning"
+          (click)="changeSectionTo('Review and Submit'); actionHelper.addNoShowAction({});"
+        >
+          No Show
+        </button>
+        <button
           class="btn btn-primary"
           [disabled]="!selectedValue"
           (click)="changeSectionTo('Pickup'); actionHelper.addStartMatchAction({position: selectedValue});"
@@ -161,14 +182,14 @@
       </div>
       <button
         *ngIf="autoPhase"
-        class="btn btn-dark"
+        class="btn btn-primary"
         (click)="autoPhase = false; actionHelper.addEndAutoPhaseAction({});"
       >
         Start Teleop
       </button>
       <button
         *ngIf="!autoPhase"
-        class="btn btn-info"
+        class="btn btn-primary"
         (click)="changeSectionTo('Endgame'); actionHelper.addEndTeleopPhaseAction({});"
       >
         Endgame
@@ -194,49 +215,48 @@
       >
         DEAD
       </button>
-      <button
-        class="btn btn-info"
-        (click)="changeSectionTo('Pickup'); actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kDROPPED});"
-      >
-        Dropped
-      </button>
-      <div *ngIf="!autoPhase" class="d-grid gap-1" style="padding: 0">
-        <div
-          style="
-            display: flex-wrap;
-            padding: 0;
-            justify-content: center;
-            text-align: center;
-            align-content: center;
-            margin: 0;
-          "
+      <div class="half-button-container">
+        <button
+          class="btn btn-info half-button"
+          (click)="changeSectionTo('Pickup'); actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kDROPPED});"
         >
+          Dropped
+        </button>
+        <button
+          class="btn btn-info half-button"
+          (click)="changeSectionTo('Pickup'); actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kOUT_OF_FIELD});"
+        >
+          Out Of Field
+        </button>
+      </div>
+      <div
+        *ngIf="!autoPhase"
+        class="d-grid gap-1"
+        style="padding: 0; margin: 0"
+      >
+        <div class="half-button-container">
           <button
-            class="btn btn-success"
+            class="btn btn-success half-button"
             (click)="changeSectionTo('Pickup'); actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kAMP});"
-            style="width: 48%; height: 12vh; margin: 0px 10px 10px 0px"
           >
             AMP
           </button>
 
           <button
-            class="btn btn-warning"
-            (click)="changeSectionTo('Pickup');  actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kAMP_AMPLIFIED});"
-            style="width: 48%; height: 12vh; margin: 0px 0px 10px 0px"
+            class="btn btn-warning half-button"
+            (click)="changeSectionTo('Pickup');  actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kSHUTTLED});"
           >
-            AMP AMPLIFIED
+            SHUTTLED
           </button>
           <button
-            class="btn btn-success"
+            class="btn btn-success half-button"
             (click)="changeSectionTo('Pickup'); actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kSPEAKER});"
-            style="width: 48%; height: 12vh; margin: 0px 10px 0px 0px"
           >
             SPEAKER
           </button>
           <button
-            class="btn btn-warning"
+            class="btn btn-warning half-button"
             (click)="changeSectionTo('Pickup');  actionHelper.addPlaceNoteAction({auto: autoPhase, scoreType: ScoreType.kSPEAKER_AMPLIFIED});"
-            style="width: 48%; height: 12vh; margin: 0px 0px 0px 0px"
           >
             SPEAKER AMPLIFIED
           </button>
@@ -283,7 +303,7 @@
         </button>
       </div>
       <button
-        class="btn btn-dark"
+        class="btn btn-primary"
         *ngIf="autoPhase"
         (click)="autoPhase = false; actionHelper.addEndAutoPhaseAction({});"
       >
@@ -291,7 +311,7 @@
       </button>
       <button
         *ngIf="!autoPhase"
-        class="btn btn-info"
+        class="btn btn-primary"
         (click)="changeSectionTo('Endgame'); actionHelper.addEndTeleopPhaseAction({});"
       >
         Endgame
@@ -384,7 +404,7 @@
       </div>
       <button
         *ngIf="!autoPhase"
-        class="btn btn-info"
+        class="btn btn-primary"
         (click)="changeSectionTo('Review and Submit');  addPenalties(); actionHelper.addEndMatchAction({stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
@@ -420,7 +440,7 @@
         Revive
       </button>
       <button
-        class="btn btn-info"
+        class="btn btn-primary"
         (click)="changeSectionTo('Review and Submit');  addPenalties(); actionHelper.addEndMatchAction({stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
@@ -430,6 +450,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">
@@ -461,6 +482,7 @@
               Mobility: {{(action.actionTaken | cast:
               MobilityActionT).mobility}}
             </span>
+            <span *ngSwitchCase="ActionType.NoShowAction">NoShow: true</span>
             <span *ngSwitchDefault>{{action.actionTakenType}}</span>
             <span *ngSwitchCase="ActionType.PenaltyAction">
               Penalties: {{(action.actionTaken | cast: