public IEnumerable <EfficiencyRecord> Compute([NotNull] IEnumerable <EfficiencyShift> shifts, LaborRate[] laborRates, DateTime start, DateTime end)
        {
            // TODO: This can be configurable
            var breakdownInterval = TimeSpan.FromHours(1);

            var results = new List <EfficiencyRecord>();

            foreach (var shift in shifts)
            {
                Transaction shiftFirstTransaction  = null;
                Transaction shiftLastTransaction   = null;
                var         shiftEfficiencyResults = new List <EfficiencyRecord>();

                var shiftIntervals = DateRangeList.GenerateAscending(shift.StartTime, shift.EndTime, breakdownInterval, nearest: true);

                foreach (var shiftInterval in shiftIntervals)
                {
                    var intervalTransactions = _transactionProvider.FindTransactions(shift.SiteCode,
                                                                                     shift.SiteEmployeeCodes, shiftInterval.Start, shiftInterval.End.AddMilliseconds(-1) //use EndOf.AddMilliseconds(-1) here so that we don't count same transaction for different intervals
                                                                                     ).ToArray();

                    if (intervalTransactions.Any())
                    {
                        if (shiftFirstTransaction == null)
                        {
                            shiftFirstTransaction = intervalTransactions.First();

                            // For the first transaction in a shift the transition time is not relevant, since
                            // we count it in the time to first transaction.
                            shiftFirstTransaction.TransitionTimeSeconds = 0;
                        }

                        shiftLastTransaction = intervalTransactions.Last();
                    }

                    var intervalEfficiencyResults = HandleInterval(shift, shiftInterval, intervalTransactions, laborRates);

                    shiftEfficiencyResults.AddRange(intervalEfficiencyResults);
                }

                shiftEfficiencyResults.ForEach(x => x.LastTransactionDate = shiftLastTransaction?.TransactionDate);

                //set time clocked-in for shifts with no transactions
                if (shiftEfficiencyResults.All(x => x.QuantityProcessed == 0))
                {
                    foreach (var effitem in shiftEfficiencyResults)
                    {
                        effitem.TimeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((effitem.IntervalEndTime - effitem.IntervalStartTime).TotalSeconds));
                    }
                }

                //set time to first for shifts with no transactions that count towards efficiency
                if (shiftEfficiencyResults.All(x => x.QuantityProcessed == 0 && x.TimeEarned.TotalSeconds == 0))
                {
                    foreach (var effitem in shiftEfficiencyResults)
                    {
                        effitem.ShiftTimeToFirstTransaction = effitem.TimeClockedIn;
                        effitem.IsNonProductiveClockIn      = shift.IsTransactional;
                    }
                }

                if (shiftEfficiencyResults.Any(x => x.QuantityProcessed > 0))
                {
                    // Set the time to the first and last transactions for this shift
                    var firstTransactionEfficiencyRecords = shiftEfficiencyResults.Where(e =>
                                                                                         (e.TransactionTypeCode == null || e.TransactionTypeCode.IgnoreCaseEquals(shiftFirstTransaction?.TransactionTypeCode)) &&
                                                                                         e.IntervalStartTime <= shiftFirstTransaction?.TransactionDate);

                    if (firstTransactionEfficiencyRecords != null && shiftFirstTransaction != null && firstTransactionEfficiencyRecords.Any())
                    {
                        foreach (var item in firstTransactionEfficiencyRecords)
                        {
                            var timeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((item.IntervalEndTime - item.IntervalStartTime).TotalSeconds));
                            var shiftTimeToFirstTransaction = item.QuantityProcessed == 0 ? timeClockedIn : item.IntervalStartToFirstTransaction;
                            item.ShiftTimeToFirstTransaction = shift.IsTransactional ? shiftTimeToFirstTransaction : new TimeSpan();
                            item.TimeClockedIn      += shiftTimeToFirstTransaction;
                            item.TransactionTypeCode = item.TransactionTypeCode ?? shiftFirstTransaction.TransactionTypeCode;
                        }
                    }

                    var lastTransactionEfficiencyRecords = shiftEfficiencyResults.Where(e =>
                                                                                        (e.TransactionTypeCode == null || e.TransactionTypeCode.IgnoreCaseEquals(shiftLastTransaction?.TransactionTypeCode)) &&
                                                                                        e.IntervalEndTime >= shiftLastTransaction?.TransactionDate);

                    if (lastTransactionEfficiencyRecords != null && shiftLastTransaction != null && lastTransactionEfficiencyRecords.Any())
                    {
                        foreach (var item in lastTransactionEfficiencyRecords)
                        {
                            var timeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((item.IntervalEndTime - item.IntervalStartTime).TotalSeconds));
                            var shiftTimeAfterLastTransaction = item.QuantityProcessed == 0 ? timeClockedIn : item.LastTransactionToIntervalEnd;
                            item.ShiftTimeAfterLastTransaction = shift.IsTransactional && !shift.IsClockedIn ? shiftTimeAfterLastTransaction : new TimeSpan();
                            item.TimeClockedIn      += shiftTimeAfterLastTransaction;
                            item.TransactionTypeCode = item.TransactionTypeCode ?? shiftLastTransaction.TransactionTypeCode;
                        }
                    }
                }

                results.AddRange(shiftEfficiencyResults
                                 .Where(x => x.TransactionTypeCode != null || x.TimeClockedIn.TotalSeconds != 0)); //filter out rows that don't have any seconds clocked in and ttype is null
            }

            SetStartEndShiftTime(results, start, end);

            return(results);
        }