func performancePlotHandlerV2(w http.ResponseWriter, r *http.Request) {
	/* Extract fields in GET request */
	line := r.URL.Query().Get("line")
	bound := r.URL.Query().Get("bound")
	origin := r.URL.Query().Get("origin")
	destination := r.URL.Query().Get("destination")
	departureTime := parseISOTime(r.URL.Query().Get("departureTime"))

	/* Error message string that functions should append to */
	errorMessage := ""

	/* Some additional info for nice display*/
	originName := selectdb.SelectStop(origin)
	destinationName := selectdb.SelectStop(destination)

	/* Find out which vehicle produced the data and list its journey history */
	vehicleId, vehicleHistory, actualJourneyTime, totalPredictions, totalStddevs := lookupVehicleHistory2(line, bound, origin, destination, departureTime, &errorMessage)

	/* Write HTML output */
	writePerformancePlotDetails2(w, PerformancePlotDetails2{
		ErrorMessage:      errorMessage,
		Line:              line,
		Bound:             bound,
		Origin:            origin,
		DepartureTime:     departureTime.Format(timeFormatString),
		OriginName:        originName,
		DestinationName:   destinationName,
		VehicleId:         vehicleId,
		VehicleHistory:    vehicleHistory,
		PredictionMethods: performanceeval.Methods(),

		ActualTime:  actualJourneyTime,
		Predictions: totalPredictions,
		Stddevs:     totalStddevs,
	})
}
/* Grabs all stored historic and real time data, produce time plots and
   also percentages of consistently delayed or early */
func DelayConsistencyPlot(startTime, endTime time.Time, wr io.Writer, templatePath string) {
	charts := make([]DelayConsistencyChart, 0)

	availableRoutes := selectdb.SelectAvailableRoutesTflPredictions()
	for _, route := range availableRoutes {
		// Extract fields from routes
		line := route["line"]
		bound := route["bound"]
		fromStop := route["fromStop"]
		toStop := route["toStop"]
		fromStopName := selectdb.SelectStop(fromStop)
		toStopName := selectdb.SelectStop(toStop)

		actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime)

		// Extract the actual departure times for generating predictions later on (discarding those that are)
		actualDepartureTimes := make([]time.Time, len(actualJourneyTimes))
		actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes))
		i := 0
		for departureTime, duration := range actualJourneyTimes {
			actualDepartureTimes[i] = departureTime
			actualJourneyTimeDurations[i] = duration
			i++
		}

		// Fetch Tfl predictions
		// tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes)

		/* Cache for real time estimates which can be reused for different prediction methods
		   map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */
		realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64)

		// Generate predictions using our methods (best historic and real time only)
		predictions := make(map[performanceeval.Method]map[time.Time]time.Duration)
		predictionsAdjacentStops := make(map[performanceeval.Method]map[time.Time]map[string]map[string]time.Duration)
		methods := []performanceeval.Method{
			performanceeval.DefaultHistoric(),
			performanceeval.DefaultRealTime(),
		}
		// Highest # consistency possible - number of pairs of adjacent stops in route
		maxScore := 0
		for _, method := range methods {
			predictions[method], predictionsAdjacentStops[method], maxScore = predictJourneyTimesWithDetails(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache)
		}

		// Convert the above into PerformanceTestSeries2
		actualJourneyTimesSeries := PerformanceTestSeries2{
			SeriesName: "Actual Time",
			X:          actualDepartureTimes,
			Y:          actualJourneyTimeDurations,
			Count:      len(actualJourneyTimeDurations),
		}
		sort.Sort(actualJourneyTimesSeries)

		/*tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes)
		sort.Sort(tflPredictionsSeries)*/

		performanceTestSeries2 := []PerformanceTestSeries2{
			actualJourneyTimesSeries,
			// tflPredictionsSeries,
		}

		// Add all prediction series into performance test series 2
		for method, predictedJouneyTimes := range predictions {
			predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes)
			sort.Sort(predictionSeries)

			performanceTestSeries2 = append(performanceTestSeries2, predictionSeries)
		}

		// Form delay consistency (percentages) series
		delayConsistencySeries := getDelayConsistencySeries(predictionsAdjacentStops[performanceeval.DefaultHistoric()],
			predictionsAdjacentStops[performanceeval.DefaultRealTime()], maxScore)

		charts = append(charts, DelayConsistencyChart{
			Line:                     line,
			Bound:                    bound,
			Origin:                   fromStop,
			Destination:              toStop,
			OriginName:               fromStopName,
			DestinationName:          toStopName,
			MultipleSeries:           performanceTestSeries2,
			MultiplePercentageSeries: []DelayConsistencySeries{delayConsistencySeries},
			ServerDomain:             credentials.GetGoServerDomain(),
		})

		logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop)
	}

	summary := DelayConsistencyPlotSummary{
		Methods: performanceeval.Methods(),
		Charts:  charts,
	}
	writeDelayConsistencyChart(wr, summary, templatePath)
}
/* Find out which vehicle produced the data and list its journey history, together with
   the total actual journey time, total predictions and total standard deviations */
func lookupVehicleHistory2(line, bound, origin, destination string, departureTime time.Time, errorMessage *string) (string, []VehicleRecord2, time.Duration, map[performanceeval.Method]time.Duration, map[performanceeval.Method]time.Duration) {
	vehicleId, arrivalTime, err := selectdb.SelectVehicleAndArrivalTimeFromJourneyHistory(line, bound, origin, destination, departureTime)
	if err != nil {
		logger.GetLogger().Panic(err)
	}
	startTime := departureTime
	endTime := arrivalTime

	vehicleHistory := selectdb.SelectVehicleHistory(vehicleId, startTime, endTime, timeFormatString)

	rawArrivalTimes := selectdb.SelectRawArrivalTimes(vehicleId, startTime, endTime, timeFormatString)

	intermediateStopsOrdered := selectdb.SelectIntermediateStops(line, bound, origin, destination)
	intermediateStops := make(map[string]map[string]bool)
	for i := 0; i < len(intermediateStopsOrdered)-1; i++ {
		intermediateStops[intermediateStopsOrdered[i]] = map[string]bool{
			intermediateStopsOrdered[i+1]: true,
		}
	}

	/* Temporarily discarded */
	// predictionsUsedRealTime := make(map[string]map[string]map[time.Time]map[string]int64)

	totalPredictions := make(map[performanceeval.Method]time.Duration)
	totalStddevs := make(map[performanceeval.Method]time.Duration)
	idvPredictions := make(map[performanceeval.Method]map[string]map[string]time.Duration)
	idvStddevs := make(map[performanceeval.Method]map[string]map[string]time.Duration)
	idvExpectedArrivalTimesShifting := make(map[performanceeval.Method]map[string]map[string]time.Time)
	idvMixedTypes := make(map[performanceeval.Method]map[string]map[string]string)

	for _, method := range performanceeval.Methods() {
		totalPrediction, totalStddev, idvPredictionsMap, idvStddevsMap, edtShifting, mixedTypes := lookupPredictionsUsed(line, bound, intermediateStops, intermediateStopsOrdered, departureTime,
			method, nil, errorMessage)

		totalPredictions[method] = totalPrediction
		totalStddevs[method] = totalStddev
		idvPredictions[method] = idvPredictionsMap
		idvStddevs[method] = idvStddevsMap
		if method.IsShiftingBased() {
			idvExpectedArrivalTimesShifting[method] = edtShifting
		}
		if method.IsMixedBased() {
			idvMixedTypes[method] = mixedTypes
		}

	}

	actualJourneyTime := performanceeval.FetchActualJourneyTime(line, bound, origin, destination, departureTime)

	/* Flag for use later, in case bus switches direction invalidate and prevent travel time details from further generated */
	firstTimeFlag := true

	result := make([]VehicleRecord2, len(vehicleHistory))
	for i, v := range vehicleHistory {
		result[i] = VehicleRecord2{
			Line:           v["line"],
			Bound:          v["bound"],
			StopSeq:        v["stopSeq"],
			StopName:       v["stopName"],
			ArrivalTime:    v["time"],
			RawArrivalTime: rawArrivalTimes[v["naptanId"]],
			NaptanId:       v["naptanId"],
			Details: generateTravelTimeDetails(line, bound, intermediateStops, departureTime, vehicleHistory, i,
				idvPredictions, idvStddevs, idvExpectedArrivalTimesShifting, idvMixedTypes, &firstTimeFlag),
		}
	}

	if !firstTimeFlag {
		*errorMessage += "Bus terminated early\n"
	}

	/*predictions := []time.Duration{
		actualJourneyTime,
		predictionThreeDays,
		predictionWeekly,
		predictionWeeklyDiscardInterpolation,
		predictionWeeklySameRouteOnly,
		predictionWeeklyShifting,
		predictionWeeklyShiftingDiscardInterpolation,
		predictionWeeklyShiftingSameRouteOnly,
		predictionRealTimeMedian,
		predictionRealTimeLastTwo,

		totalStddevThreeDays,
		totalStddevWeekly,
		totalStddevWeeklyDiscardInterpolation,
		totalStddevWeeklySameRouteOnly,
		totalStddevWeeklyShifting,
		totalStddevWeeklyShiftingDiscardInterpolation,
		totalStddevWeeklyShiftingSameRouteOnly,
		totalStddevRealTimeMedian,
		totalStddevRealTimeLastTwo,
	}*/

	return vehicleId, result, actualJourneyTime, totalPredictions, totalStddevs
}
func generateTravelTimeDetails(line, bound string, intermediateStops map[string]map[string]bool, departureTime time.Time,
	vehicleHistory []map[string]string, idx int,
	idvPredictions map[performanceeval.Method]map[string]map[string]time.Duration,
	idvStddevs map[performanceeval.Method]map[string]map[string]time.Duration,
	expectedArrivalTimesShifting map[performanceeval.Method]map[string]map[string]time.Time,
	mixedTypesAtEachStop map[performanceeval.Method]map[string]map[string]string,

	firstTimeFlag *bool) TravelTimeDetails {

	/* Cannot generate travel time details if it is the first row (since no previous stop) */
	if idx == 0 {
		return TravelTimeDetails{

			TimeTaken:   time.Duration(0),
			Predictions: nil,
			Stddevs:     nil,
			ExpectedArrivalTimesShifting: nil,
			MixedTypes:                   nil,
		}
	}

	fromTime := parseTime(vehicleHistory[idx-1]["time"])
	toTime := parseTime(vehicleHistory[idx]["time"])
	timeTaken := toTime.Sub(fromTime)

	checkline := vehicleHistory[idx]["line"]
	checkBound := vehicleHistory[idx]["bound"]

	/* If changed direction, invalidate flag and return nil, so that no further travel time details are generated */
	if line != checkline || bound != checkBound || !*firstTimeFlag {
		*firstTimeFlag = false

		return TravelTimeDetails{
			TimeTaken:   timeTaken,
			Predictions: nil,
			Stddevs:     nil,
			ExpectedArrivalTimesShifting: nil,
			MixedTypes:                   nil,
		}
	}

	fromStop := vehicleHistory[idx-1]["naptanId"]
	toStop := vehicleHistory[idx]["naptanId"]
	fromStopSeq, _ := strconv.Atoi(vehicleHistory[idx-1]["stopSeq"])
	toStopSeq, _ := strconv.Atoi(vehicleHistory[idx]["stopSeq"])

	if fromStopSeq+1 != toStopSeq {
		return TravelTimeDetails{
			TimeTaken:   timeTaken,
			Predictions: nil,
			Stddevs:     nil,
			ExpectedArrivalTimesShifting: nil,
			MixedTypes:                   nil,
		}
	}

	predictions := make(map[performanceeval.Method]time.Duration)
	stddevs := make(map[performanceeval.Method]time.Duration)
	edtsShifting := make(map[performanceeval.Method]time.Time)
	mixedTypes := make(map[performanceeval.Method]string)

	for _, method := range performanceeval.Methods() {
		predictions[method] = idvPredictions[method][fromStop][toStop]
		stddevs[method] = idvStddevs[method][fromStop][toStop]
		if method.IsShiftingBased() {
			edtsShifting[method] = expectedArrivalTimesShifting[method][fromStop][toStop]
		}
		if method.IsMixedBased() {
			mixedTypes[method] = mixedTypesAtEachStop[method][fromStop][toStop]
		}
	}

	return TravelTimeDetails{
		TimeTaken:   timeTaken,
		Predictions: predictions,
		Stddevs:     stddevs,
		ExpectedArrivalTimesShifting: edtsShifting,
		MixedTypes:                   mixedTypes,
	}
}
			return fmt.Sprintf("%v (s.d. %v)", predictions[method].String(), stddevs[method].String())
		},
		"formatErrorMessage": func(errorMessage string) string {
			return strings.Replace(errorMessage, "\n", "<br>", -1)
		},
		"formatEAT": func(method performanceeval.Method, expectedArrivalTimes map[performanceeval.Method]time.Time) string {
			expectedArrivalTime, exists := expectedArrivalTimes[method]
			if exists {
				return formatTime(expectedArrivalTime)
			} else {
				return ""
			}
		},
		"getTargetColumnArray": func() string {
			startColumn := 5
			columns := make([]string, len(performanceeval.Methods()))
			for i := 0; i < len(performanceeval.Methods()); i++ {
				columns[i] = fmt.Sprintf("%v", startColumn+i)
			}
			return "[" + strings.Join(columns, ",") + "]"
		},
	}
)

type PerformancePlotDetails2 struct {
	ErrorMessage      string
	Line              string
	Bound             string
	Origin            string
	DepartureTime     string
	OriginName        string
/* Grabs all stored data to produce time plots */
func PerformancePlot2(startTime, endTime time.Time, wr io.Writer, templatePath string) {
	charts := make([]PerformanceChart2, 0)

	availableRoutes := selectdb.SelectAvailableRoutesTflPredictions()
	for _, route := range availableRoutes {
		// Extract fields from routes
		line := route["line"]
		bound := route["bound"]
		fromStop := route["fromStop"]
		toStop := route["toStop"]
		fromStopName := selectdb.SelectStop(fromStop)
		toStopName := selectdb.SelectStop(toStop)

		actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime)

		// Extract the actual departure times for generating predictions later on (discarding those that are)
		actualDepartureTimes := make([]time.Time, len(actualJourneyTimes))
		actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes))
		i := 0
		for departureTime, duration := range actualJourneyTimes {
			actualDepartureTimes[i] = departureTime
			actualJourneyTimeDurations[i] = duration
			i++
		}

		// Fetch Tfl predictions
		tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes)

		/* Cache for real time estimates which can be reused for different prediction methods
		   map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */
		realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64)

		// Generate predictions using our methods
		predictions := make(map[performanceeval.Method]map[time.Time]time.Duration)
		for _, method := range performanceeval.Methods() {
			predictions[method] = performanceeval.PredictJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache)
		}

		// Convert the above into PerformanceTestSeries2
		actualJourneyTimesSeries := PerformanceTestSeries2{
			SeriesName: "Actual Time",
			X:          actualDepartureTimes,
			Y:          actualJourneyTimeDurations,
			Count:      len(actualJourneyTimeDurations),
		}
		sort.Sort(actualJourneyTimesSeries)

		tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes)
		sort.Sort(tflPredictionsSeries)

		performanceTestSeries2 := []PerformanceTestSeries2{
			actualJourneyTimesSeries,
			tflPredictionsSeries,
		}

		// Add all prediction series into performance test series 2
		for method, predictedJouneyTimes := range predictions {
			predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes)
			sort.Sort(predictionSeries)

			performanceTestSeries2 = append(performanceTestSeries2, predictionSeries)
		}

		charts = append(charts, PerformanceChart2{
			Line:            line,
			Bound:           bound,
			Origin:          fromStop,
			Destination:     toStop,
			OriginName:      fromStopName,
			DestinationName: toStopName,
			MultipleSeries:  performanceTestSeries2,
			ServerDomain:    credentials.GetGoServerDomain(),
		})

		logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop)
	}

	summary := PerformancePlotSummary2{
		Methods: performanceeval.Methods(),
		Charts:  charts,
	}
	writePerformanceChart2(wr, summary, templatePath)
}