public void InsertAveragePenaltyCost(AveragePenaltyCostPerJam average)
 {
     string query = s_DeleteAveragePenaltyCostQuery + s_InsertAveragePenaltyCostQuery;
     using (var cmd = new SqlCommand(query, _connection, _transaction))
     {
         cmd.Parameters.Clear();
         cmd.Parameters.Add("@JammerPointCost", SqlDbType.Float).Value = average.JammerPointCost;
         cmd.Parameters.Add("@BlockerPointCost", SqlDbType.Float).Value = average.BlockerPointCost;
         cmd.Parameters.Add("@JammerValueCost", SqlDbType.Float).Value = average.JammerValueCost;
         cmd.Parameters.Add("@BlockerValueCost", SqlDbType.Float).Value = average.BlockerValueCost;
         cmd.ExecuteNonQuery();
     }
 }
        private static Dictionary<int, PlayerPerformance> GeneratePlayerPerformanceIteration(Dictionary<int, PlayerPerformance> previousIteration,
                                                                                  IOrderedEnumerable<Jam> jams,
                                                                                  Dictionary<int, Dictionary<int, JamTeamData>> jamData,
                                                                                  Dictionary<int, double> jamTotalPortionMap,
                                                                                  Dictionary<int, Dictionary<int, JamPlayerEffectiveness>> jpe,
                                                                                  AveragePenaltyCostPerJam avgPenCost)
        {
            Dictionary<int, PlayerPerformance> pps = new Dictionary<int, PlayerPerformance>();
            foreach(Jam jam in jams)
            {
                var pe = jpe[jam.ID];
                var grouped = pe.Values.GroupBy(p => p.TeamID);
                var team1Players = grouped.First().Select(p => previousIteration[p.PlayerID]);
                var team2Players = grouped.Last().Select(p => previousIteration[p.PlayerID]);
                IEnumerable<JamPlayerEffectiveness> jammers = pe.Values.Where(eff => eff.IsJammer);
                double team1Score = CalculateExpectedScore(team1Players, jammers, jam.BoutID);
                int team1ID = grouped.First().Key;
                double team2Score = CalculateExpectedScore(team2Players, jammers, jam.BoutID);

                foreach(var eff in pe.Values)
                {
                    var previousPerformance = previousIteration[eff.PlayerID];
                    // get/set PlayerPerformance object
                    if (!pps.ContainsKey(eff.PlayerID))
                    {
                        pps[eff.PlayerID] = new PlayerPerformance
                        {
                            Player = previousPerformance.Player,
                            Bouts = new List<BoutPerformance>()
                        };
                    }
                    PlayerPerformance curPP = pps[eff.PlayerID];

                    // get/set BoutPerformance object
                    var prevBout = previousPerformance.Bouts.First(b => b.BoutID == jam.BoutID);
                    BoutPerformance bp = null;
                    if (!curPP.Bouts.Any() ||
                        curPP.Bouts.Last().BoutID != prevBout.BoutID )
                    {
                        bp = new BoutPerformance
                        {
                            BoutID = prevBout.BoutID,
                            AwayTeamName = prevBout.AwayTeamName,
                            HomeTeamName = prevBout.HomeTeamName,
                            BoutDate = prevBout.BoutDate,
                            Jams = new List<JamPerformance>()
                        };
                        if (prevBout.BlockerPerformance != null)
                        {
                            bp.BlockerPerformance = new RolledUpPerformanceData
                            {
                                TotalJamPortions = prevBout.BlockerPerformance.TotalJamPortions,
                                TotalPenalties = prevBout.BlockerPerformance.TotalPenalties,
                                TotalPenaltyCost = prevBout.BlockerPerformance.TotalPenaltyCost
                            };
                        }
                        if(prevBout.JammerPerformance != null)
                        {
                            bp.JammerPerformance = new RolledUpPerformanceData
                            {
                                TotalJamPortions = prevBout.JammerPerformance.TotalJamPortions,
                                TotalPenalties = prevBout.JammerPerformance.TotalPenalties,
                                TotalPenaltyCost = prevBout.JammerPerformance.TotalPenaltyCost
                            };
                        }
                        curPP.Bouts.Add(bp);
                    }
                    else
                    {
                        bp = curPP.Bouts.Last();
                    }

                    JamTeamData jd = jamData[jam.ID][eff.TeamID];
                    JamPerformance prevJamPerformance = prevBout.Jams.First(j => j.JamID == jam.ID);
                    double pointDifferential = 0;
                    var playerPerf = prevJamPerformance.JammerJamPercentage > 0 ? previousPerformance.JammerPerformance : previousPerformance.BlockerPerformance;
                    double playerScore = playerPerf.TotalPointsVersusMedian / playerPerf.TotalJamPortions;
                    if(curPP.Player.TeamID == team1ID)
                    {
                        pointDifferential = jd.PointDelta - team1Score + team2Score + playerScore;
                    }
                    else
                    {
                        pointDifferential = jd.PointDelta - team2Score + team1Score + playerScore;
                    }
                    JamPerformance jp = new JamPerformance
                    {
                        BlockerJamPercentage = prevJamPerformance.BlockerJamPercentage,
                        DeltaPercentile = prevJamPerformance.DeltaPercentile,
                        IsFirstHalf = prevJamPerformance.IsFirstHalf,
                        JamID = prevJamPerformance.JamID,
                        JammerJamPercentage = prevJamPerformance.JammerJamPercentage,
                        JamNumber = prevJamPerformance.JamNumber,
                        JamPenalties = prevJamPerformance.JamPenalties,
                        MedianDelta = prevJamPerformance.MedianDelta,
                        PenaltyCost = prevJamPerformance.PenaltyCost,
                        PointDelta = jd.PointDelta,
                        DeltaPortionVersusMedian = pointDifferential
                        //PlayerValue
                    };
                    bp.Jams.Add(jp);
                }
            }

            Parallel.ForEach(pps.Values, pp =>
            {
                RollUpIteratedPlayerPerformance(avgPenCost, jamTotalPortionMap, pp);
            });

            return pps;
        }
        private static void RollUpPlayerPerformance(AveragePenaltyCostPerJam avgPenCost, Dictionary<int, double> jamTotalPortionMap, PlayerPerformance pp)
        {
            pp.BlockerPerformance = new RolledUpPerformanceData();
            pp.JammerPerformance = new RolledUpPerformanceData();
            foreach (BoutPerformance bp in pp.Bouts)
            {
                bp.BlockerPerformance = new RolledUpPerformanceData();
                bp.JammerPerformance = new RolledUpPerformanceData();
                foreach (JamPerformance jp in bp.Jams)
                {
                    double averagePenaltyCost = jp.JammerJamPercentage > 0 ? avgPenCost.JammerPointCost : avgPenCost.BlockerPointCost;
                    double jamShare = (jp.JammerJamPercentage * 4 + jp.BlockerJamPercentage) / jamTotalPortionMap[jp.JamID];
                    jp.DeltaPortionVersusMedian = (jp.PointDelta - jp.MedianDelta) * jamShare;
                    jp.PlayerValue = jp.DeltaPortionVersusMedian - jp.PenaltyCost + averagePenaltyCost;
                    var rollUp = jp.JammerJamPercentage > 0 ? bp.JammerPerformance : bp.BlockerPerformance;
                    rollUp.TotalJamPortions += jp.JammerJamPercentage + jp.BlockerJamPercentage;
                    rollUp.TotalPenalties += jp.JamPenalties;
                    rollUp.TotalPenaltyCost += jp.PenaltyCost;
                    rollUp.TotalPointsVersusMedian += jp.DeltaPortionVersusMedian;
                    rollUp.TotalPlayerValue += jp.PlayerValue;

                }

                pp.BlockerPerformance.TotalJamPortions += bp.BlockerPerformance.TotalJamPortions;
                pp.BlockerPerformance.TotalPenalties += bp.BlockerPerformance.TotalPenalties;
                pp.BlockerPerformance.TotalPenaltyCost += bp.BlockerPerformance.TotalPenaltyCost;
                pp.BlockerPerformance.TotalPointsVersusMedian += bp.BlockerPerformance.TotalPointsVersusMedian;
                pp.BlockerPerformance.TotalPlayerValue += bp.BlockerPerformance.TotalPlayerValue;

                pp.JammerPerformance.TotalJamPortions += bp.JammerPerformance.TotalJamPortions;
                pp.JammerPerformance.TotalPenalties += bp.JammerPerformance.TotalPenalties;
                pp.JammerPerformance.TotalPenaltyCost += bp.JammerPerformance.TotalPenaltyCost;
                pp.JammerPerformance.TotalPointsVersusMedian += bp.JammerPerformance.TotalPointsVersusMedian;
                pp.JammerPerformance.TotalPlayerValue += bp.JammerPerformance.TotalPlayerValue;
            }
        }
        private static Dictionary<int, PlayerPerformance> GenerateInitialPlayerPerformance(Dictionary<int, Dictionary<int, Player>> players, 
                                                                                IOrderedEnumerable<Jam> jams, 
                                                                                Dictionary<int, Dictionary<int, JamPlayerEffectiveness>> jpe, 
                                                                                Dictionary<int, Dictionary<int, JamTeamData>> jamData, 
                                                                                Dictionary<int, Team> teams, 
                                                                                Dictionary<int, Bout> bouts, 
                                                                                Dictionary<int, Dictionary<int, List<Penalty>>> penalties, 
                                                                                IList<PenaltyGroup> pgs, 
                                                                                AveragePenaltyCostPerJam avgPenCost, 
                                                                                Dictionary<FoulComparison, float> medians, 
                                                                                Dictionary<int, double> groupPenaltyCostMap, 
                                                                                Dictionary<int, double> jamTotalPortionMap)
        {
            Dictionary<int, PlayerPerformance> pps = new Dictionary<int, PlayerPerformance>();
            foreach (Jam jam in jams)
            {
                var pe = jpe[jam.ID];
                foreach(var eff in pe.Values)
                {
                    // get/set PlayerPerformance object
                    if (!pps.ContainsKey(eff.PlayerID))
                    {
                        pps[eff.PlayerID] = new PlayerPerformance
                        {
                            Player = players[eff.PlayerID][eff.TeamID],
                            Bouts = new List<BoutPerformance>()
                        };
                    }
                    PlayerPerformance curPP = pps[eff.PlayerID];

                    // get/set BoutPerformance object
                    Bout bout = bouts[jam.BoutID];
                    BoutPerformance bp = null;
                    if (!curPP.Bouts.Any() ||
                        curPP.Bouts.Last().BoutDate != bout.BoutDate ||
                        curPP.Bouts.Last().HomeTeamName != teams[bout.HomeTeamID].Name ||
                        curPP.Bouts.Last().AwayTeamName != teams[bout.AwayTeamID].Name)
                    {
                        bp = new BoutPerformance
                        {
                            BoutID = bout.ID,
                            AwayTeamName = teams[bout.AwayTeamID].Name,
                            HomeTeamName = teams[bout.HomeTeamID].Name,
                            BoutDate = bout.BoutDate,
                            Jams = new List<JamPerformance>()
                        };
                        curPP.Bouts.Add(bp);
                    }
                    else
                    {
                        bp = curPP.Bouts.Last();
                    }

                    JamTeamData jd = jamData[jam.ID][eff.TeamID];
                    int penaltyCount = penalties.ContainsKey(jam.ID) && penalties[jam.ID].ContainsKey(eff.PlayerID) ? penalties[jam.ID][eff.PlayerID].Count() : 0;
                    JamPerformance jp = new JamPerformance
                    {
                        BlockerJamPercentage = eff.IsJammer ? 0 : eff.JamPortion,
                        DeltaPercentile = eff.BaseQuality,
                        IsFirstHalf = jam.IsFirstHalf,
                        JamID = jam.ID,
                        JammerJamPercentage = eff.IsJammer ? eff.JamPortion : 0,
                        JamNumber = jam.JamNumber,
                        JamPenalties = penaltyCount,
                        MedianDelta = medians[jd.FoulComparison],
                        PenaltyCost = 0,
                        PointDelta = jd.PointDelta
                    };
                    if (jamTotalPortionMap.ContainsKey(jam.ID))
                    {
                        jamTotalPortionMap[jam.ID] += eff.IsJammer ? eff.JamPortion * 4 : eff.JamPortion;
                    }
                    else
                    {
                        jamTotalPortionMap[jam.ID] = eff.IsJammer ? eff.JamPortion * 4 : eff.JamPortion;
                    }
                    bp.Jams.Add(jp);
                };
            }

            foreach (PenaltyGroup pg in pgs)
            {
                foreach (Penalty p in pg.Penalties)
                {
                    JamPerformance jp = pps[p.PlayerID].Bouts.SelectMany(b => b.Jams).Where(j => j.JamID == p.JamID).First();
                    jp.PenaltyCost += groupPenaltyCostMap[pg.GroupID];
                }
            }

            Parallel.ForEach(pps.Values, pp =>
            {
                RollUpPlayerPerformance(avgPenCost, jamTotalPortionMap, pp);
            });

            return pps;
        }