diff --git a/errors/dawn_errors.go b/errors/dawn_errors.go index 85b1433171ef62179ee05793cc9c4866438a787c..a20d95d21f9e3119746b7147641e8332d7db61c9 100644 --- a/errors/dawn_errors.go +++ b/errors/dawn_errors.go @@ -2,9 +2,12 @@ package errors import ( "fmt" + "log" + "os" + "strconv" + "github.com/gofiber/fiber/v2" "github.com/spf13/viper" - "log" ) type BaseError interface { @@ -14,6 +17,7 @@ type BaseError interface { type DawnError struct { Name string `json:"name"` Description string `json:"description"` + LogDetails string `json:"log_details,omitempty"` Code int `json:"code"` } @@ -38,26 +42,44 @@ func (err *DawnError) BuildStandardError(ctx *fiber.Ctx) StandardError { return StandardError{Source: viper.GetString("app.name"), ErrorCode: err.Name, Description: err.Description, Details: details} } +func (err *DawnError) AddLogDetails(logDetails string) *DawnError { + err.LogDetails = logDetails + return err +} + func (err *StandardError) LogJson() { log.Println(err) } +func (err *DawnError) LogString(c *fiber.Ctx) { + requestId := c.Locals("requestId") + output := strconv.Itoa(os.Getpid()) + " " + fmt.Sprintf("%s", requestId) + " " + strconv.Itoa(err.Code) + " - " + c.Method() + " " + c.Route().Path + " - " + err.Error() + " - " + err.LogDetails + fmt.Println(output) +} + func INVALID_PRODUCT(passedProduct string) *DawnError { return &DawnError{ - "INVALID_PRODUCT", - "Product type '" + passedProduct + "' is invalid", - 400, + Name: "INVALID_PRODUCT", + Description: "Product type '" + passedProduct + "' is invalid", + Code: 400, } } var BAD_REQUEST = &DawnError{ - "BAD_REQUEST", - "Bad request was submitted", - 400, + Name: "BAD_REQUEST", + Description: "Bad request was submitted", + Code: 400, } var NO_DATA_FOUND = &DawnError{ - "NO_DATA_FOUND", - "No data found for request", - 404, + Name: "NO_DATA_FOUND", + Description: "No data found for request", + Code: 404, +} + +// 500s +var DATE_PARSE_FAILURE = &DawnError{ + Name: "DATE_PARSE_FAILURE", + Description: "Date parse failure", + Code: 500, } diff --git a/main.go b/main.go index 536e9e48be7781b5d996874de2249c69992705a4..af3dadecb3ace02692546e5726fecd4e18ead8e7 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "dawn-weather/config" "dawn-weather/errors" "dawn-weather/persistence" + "fmt" "github.com/ansrivas/fiberprometheus/v2" swagger "github.com/arsmn/fiber-swagger/v2" @@ -67,8 +68,11 @@ func createFiberConfig() fiber.Config { if e, ok := err.(*errors.DawnError); ok { code = e.Code message = err.(*errors.DawnError).BuildStandardError(ctx) + err.(*errors.DawnError).LogString(ctx) + } else { + fmt.Println(err) } - message.LogJson() + // message.LogJson() err = ctx.Status(code).JSON(message) diff --git a/models/csv.go b/models/csv.go index cb3dd7bdb19dbbf41430ca3150429b95bb2c2b15..a71ec0a0d8ba80bd83124adb02afe0d3ffe8a675 100644 --- a/models/csv.go +++ b/models/csv.go @@ -32,7 +32,7 @@ type CSVRequest struct { Primary bool `json:"primary"` ComparisonYear int `json:"comparison_year"` Product string `json:"product"` - Range int `json:"range"` + Range float64 `json:"range"` Temperature int `json:"temperature"` Year int `json:"year"` Latitude float64 `json:"latitude"` @@ -76,6 +76,7 @@ func (r CSVRequest) Build(c *fiber.Ctx) CSVRequest { latitude, _ := strconv.ParseFloat(c.Query("latitude", "-1000.0"), 64) longitude, _ := strconv.ParseFloat(c.Query("longitude", "-1000.0"), 64) + _range, _ := strconv.ParseFloat(c.Query("range", "-1000.0"), 64) newRequest := CSVRequest{ Analog: parseBool(c.Query("analog")), @@ -93,7 +94,7 @@ func (r CSVRequest) Build(c *fiber.Ctx) CSVRequest { ComparisonYear: atoi(c.Query("comparison_year", "0")), Product: c.Query("product", "NONE"), - Range: atoi(c.Query("range", "0")), + Range: _range, Temperature: atoi(c.Query("temperature", "0")), Year: atoi(c.Query("year", "0")), Latitude: latitude, diff --git a/services/data_download_service.go b/services/data_download_service.go index 723782382c341a647011cbf850053fc4517287bf..7b3544e4de944f854ef151d71d49f498be576ceb 100644 --- a/services/data_download_service.go +++ b/services/data_download_service.go @@ -3,6 +3,7 @@ package services import ( "dawn-weather/errors" "dawn-weather/models" + "dawn-weather/models/enums" "dawn-weather/persistence" "dawn-weather/persistence/entities" "encoding/csv" @@ -12,47 +13,56 @@ import ( "math" "os" "strconv" + "strings" "time" "github.com/google/uuid" ) +type CSVData struct { + Date []time.Time + Analog []float64 + Cfs []float64 + CfsLower []float64 + CfsUpper []float64 + Comparison []float64 + FirstFreezing []int + LastFreezing []int + GEFS []float64 + Maximum []float64 + Minimum []float64 + Normals []float64 + Primary []float64 +} + func fillKeys(request models.CSVRequest) []string { keys := []string{} - if request.Analog { - keys = append(keys, "Date") - } + keys = append(keys, "Date") if request.Analog { keys = append(keys, "Analog Year GDD") } - // if request.Cfs { - // keys = append(keys, "CFS GDD") - // } - // if request.CfsLower { - // keys = append(keys, "CFS Lower Boundary GDD") - // } - // if request.CfsUpper { - // keys = append(keys, "CFS Upper Boundary GDD") - // } - // if request.Comparison { - // keys = append(keys, "Comparison Year GDD") - // } - // if request.FirstFreezing { - // keys = append(keys, "First Freezing Date Counts") - // } - // if request.Gefs { - // keys = append(keys, "GEFS GDD") - // } - // if request.LastFreezing { - // keys = append(keys, "Last Freezing Date Counts") - // } - // if request.Maximum { - // keys = append(keys, "Confidence Interval Upper Boundary") - // } - // if request.Minimum { - // keys = append(keys, "Confidence Interval Lower Boundary") - // } + if request.Cfs || request.CfsLower || request.CfsUpper { + keys = append(keys, "CFS GDD") + keys = append(keys, "CFS Lower Boundary GDD") + keys = append(keys, "CFS Upper Boundary GDD") + } + if request.Comparison { + keys = append(keys, "Comparison Year GDD ("+strconv.Itoa(request.ComparisonYear)+")") + } + if request.FirstFreezing || request.LastFreezing { + keys = append(keys, "First Freezing Date Counts") + keys = append(keys, "Last Freezing Date Counts") + } + if request.Gefs { + keys = append(keys, "GEFS GDD") + } + if request.Maximum { + keys = append(keys, "Confidence Interval Upper Boundary") + } + if request.Minimum { + keys = append(keys, "Confidence Interval Lower Boundary") + } if request.Normals { keys = append(keys, "Thirty Year Normal GDD") } @@ -137,6 +147,282 @@ func getAnalogYear(request models.CSVRequest, dates []time.Time) []float64 { return gddValues } +func _parseDate(date string) time.Time { + const layout = "2006-Jan-02" + newDate, e := time.Parse(layout, date) + if e != nil { + panic(errors.DATE_PARSE_FAILURE.AddLogDetails(e.Error())) + } + return newDate +} + +func parseDate(date string) time.Time { + + split := strings.Split(date, "-") + day := split[1] + switch split[0] { + case "01": + return _parseDate("1981-Jan-" + day) + case "02": + return _parseDate("1981-Feb-" + day) + case "03": + return _parseDate("1981-Mar-" + day) + case "04": + return _parseDate("1981-Apr-" + day) + case "05": + return _parseDate("1981-May-" + day) + case "06": + return _parseDate("1981-Jun-" + day) + case "07": + return _parseDate("1981-Jul-" + day) + case "08": + return _parseDate("1981-Aug-" + day) + case "09": + return _parseDate("1981-Sep-" + day) + case "10": + return _parseDate("1981-Oct-" + day) + case "11": + return _parseDate("1981-Nov-" + day) + case "12": + return _parseDate("1981-Dev-" + day) + } + panic(errors.DATE_PARSE_FAILURE.AddLogDetails("Failed converting " + date + " to proper format.")) +} + +func getFreezingDates(request models.CSVRequest, dates []time.Time) [][]int { + + freezingRequest := models.FreezingDateRequest{ + Latitude: request.Latitude, + Longitude: request.Longitude, + FreezingTemp: request.Temperature, + } + + response := GetFreezingDate(freezingRequest) + + firstFreezingDates := make(map[time.Time]int) + lastFreezingDates := make(map[time.Time]int) + for k, v := range response.LastDateCounts { + date := parseDate(k) + lastFreezingDates[date] = v + } + for k, v := range response.FirstDateCounts { + date := parseDate(k) + firstFreezingDates[date] = v + } + + date := time.Date(1981, time.January, 1, 0, 0, 0, 0, time.UTC) + lastFreezingValues := []int{} + firstFreezingValues := []int{} + for i := 0; i < 366; i++ { + if val, ok := lastFreezingDates[date]; ok { + lastFreezingValues = append(lastFreezingValues, val) + firstFreezingValues = append(firstFreezingValues, int(math.NaN())) + } else if val, ok := firstFreezingDates[date]; ok { + firstFreezingValues = append(firstFreezingValues, val) + lastFreezingValues = append(lastFreezingValues, int(math.NaN())) + } else { + firstFreezingValues = append(firstFreezingValues, int(math.NaN())) + lastFreezingValues = append(lastFreezingValues, int(math.NaN())) + } + date = date.Add(time.Duration(24) * time.Hour) + } + return [][]int{firstFreezingValues, lastFreezingValues} +} + +func getComparisonYear(request models.CSVRequest, dates []time.Time) []float64 { + gddRequest := models.GddRequest{ + Year: request.ComparisonYear, + Product: request.Product, + Latitude: request.Latitude, + Longitude: request.Longitude, + Accumulate: true, + } + + gddValues := GetGddValues(gddRequest).GddValues + for len(gddValues) < 365 { + gddValues = append(gddValues, math.NaN()) + } + return gddValues +} + +func getConfidenceInterval(request models.CSVRequest, dates []time.Time) [][]float64 { + + ciRequest := models.ConfidenceIntervalRequest{ + Interval: request.Range, + Product: enums.GetProductFromString(request.Product), + Latitude: request.Latitude, + Longitude: request.Longitude, + } + + response := GetConfidenceInterval(ciRequest) + + return [][]float64{response.LowerBound, response.UpperBound} +} + +func getCfsData(request models.CSVRequest, dates []time.Time) [][]float64 { + + gddRequest := models.GddRequest{ + Year: request.Year, + Product: request.Product, + Latitude: request.Latitude, + Longitude: request.Longitude, + Accumulate: true, + } + + response := GetCfsGddValues(gddRequest) + fullGddValues := []float64{} + fullLowerBound := []float64{} + fullUpperBound := []float64{} + in := false + after := false + c := 0 + date := time.Date(response.FirstDate.Year(), time.January, 1, 0, 0, 0, 0, time.UTC) + for i := 0; i < 366; i++ { + if response.FirstDate.After(date) { + fullGddValues = append(fullGddValues, math.NaN()) + fullLowerBound = append(fullLowerBound, math.NaN()) + fullUpperBound = append(fullUpperBound, math.NaN()) + } else { + if in || response.FirstDate == date { + if c >= len(response.GddValues) { + in = false + after = true + } else { + in = true + fullGddValues = append(fullGddValues, response.GddValues[c]) + fullLowerBound = append(fullLowerBound, response.LowerBound[c]) + fullUpperBound = append(fullUpperBound, response.UpperBound[c]) + c += 1 + } + } else if after { + fullGddValues = append(fullGddValues, math.NaN()) + fullLowerBound = append(fullLowerBound, math.NaN()) + fullUpperBound = append(fullUpperBound, math.NaN()) + } + } + date = date.Add(time.Duration(24) * time.Hour) + + } + return [][]float64{fullGddValues, fullLowerBound, fullUpperBound} +} + +func getGefsData(request models.CSVRequest, dates []time.Time) []float64 { + + gddRequest := models.GddRequest{ + Year: request.Year, + Product: request.Product, + Latitude: request.Latitude, + Longitude: request.Longitude, + Accumulate: true, + } + + response := GetGefsGddValues(gddRequest) + fullGddValues := []float64{} + in := false + after := false + c := 0 + date := time.Date(response.FirstDate.Year(), time.January, 1, 0, 0, 0, 0, time.UTC) + for i := 0; i < 366; i++ { + if response.FirstDate.After(date) { + fullGddValues = append(fullGddValues, math.NaN()) + } else { + if in || response.FirstDate == date { + if c >= len(response.GddValues) { + in = false + after = true + } else { + in = true + fullGddValues = append(fullGddValues, response.GddValues[c]) + c += 1 + } + } else if after { + fullGddValues = append(fullGddValues, math.NaN()) + } + } + date = date.Add(time.Duration(24) * time.Hour) + + } + return fullGddValues +} + +func pullData(request models.CSVRequest) CSVData { + returnData := CSVData{} + dates := fillDates() + returnData.Date = dates + + if request.Analog { + returnData.Analog = getAnalogYear(request, dates) + } + if request.Cfs || request.CfsLower || request.CfsUpper { + t := getCfsData(request, dates) + returnData.Cfs = t[0] + returnData.CfsLower = t[1] + returnData.CfsUpper = t[2] + } + if request.Comparison { + returnData.Comparison = getComparisonYear(request, dates) + } + if request.FirstFreezing || request.LastFreezing { + t := getFreezingDates(request, dates) + returnData.FirstFreezing = t[0] + returnData.LastFreezing = t[1] + } + if request.Gefs { + returnData.GEFS = getGefsData(request, dates) + } + if request.Maximum || request.Minimum { + ci := getConfidenceInterval(request, dates) + returnData.Maximum = ci[1] + returnData.Minimum = ci[0] + } + if request.Normals { + returnData.Normals = getNormals(request, dates) + } + if request.Primary { + returnData.Primary = getPrimary(request, dates) + } + + return returnData +} + +func createRecords(keys []string, data CSVData) [][]string { + records := [][]string{keys} + for i := 1; i < 366; i++ { + temp := []string{timeToString(data.Date[i-1])} + if len(data.Analog) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Analog[i-1])) + } + if len(data.Cfs) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Cfs[i-1])) + temp = append(temp, fmt.Sprintf("%f", data.CfsLower[i-1])) + temp = append(temp, fmt.Sprintf("%f", data.CfsUpper[i-1])) + } + if len(data.Comparison) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Comparison[i-1])) + } + if len(data.FirstFreezing) != 0 { + temp = append(temp, fmt.Sprintf("%d", data.FirstFreezing[i-1])) + temp = append(temp, fmt.Sprintf("%d", data.LastFreezing[i-1])) + } + if len(data.GEFS) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.GEFS[i-1])) + } + if len(data.Maximum) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Maximum[i-1])) + temp = append(temp, fmt.Sprintf("%f", data.Minimum[i-1])) + } + if len(data.Normals) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Normals[i-1])) + } + if len(data.Primary) != 0 { + temp = append(temp, fmt.Sprintf("%f", data.Primary[i-1])) + } + + records = append(records, temp) + } + return records +} + func GetDataDownload(request models.CSVRequest) string { fileId := uuid.New() @@ -150,21 +436,10 @@ func GetDataDownload(request models.CSVRequest) string { w := csv.NewWriter(f) keys := fillKeys(request) - dates := fillDates() - primary := getPrimary(request, dates) - normals := getNormals(request, dates) - analog := getAnalogYear(request, dates) - records := [][]string{keys} + data := pullData(request) - for i := 1; i < 366; i++ { - records = append(records, - []string{timeToString(dates[i-1]), - fmt.Sprintf("%f", analog[i-1]), - fmt.Sprintf("%f", primary[i-1]), - fmt.Sprintf("%f", normals[i-1]), - }) - } + records := createRecords(keys, data) for _, record := range records { if err := w.Write(record); err != nil { @@ -176,6 +451,9 @@ func GetDataDownload(request models.CSVRequest) string { fileText, err := ioutil.ReadFile(fileId.String() + ".csv") f.Close() - os.Remove(fileId.String() + ".csv") + e := os.Remove(fileId.String() + ".csv") + if e != nil { + log.Fatal(e) + } return string(fileText) } diff --git a/services/freezing_date_service.go b/services/freezing_date_service.go index 72add6366de082a08e676b44664f5ca655f099d0..5bb6c546c38f11186229863e1e0047c9feeded0e 100644 --- a/services/freezing_date_service.go +++ b/services/freezing_date_service.go @@ -31,7 +31,6 @@ func isLeapYear(year int) bool { func GetFreezingDate(request models.FreezingDateRequest) models.FreezingDateResponse { - // product := enums.GetProductFromString(request.Product) var freezingDates entities.FreezingDates freezingDates = persistence.FindFreezingDates(request.BuildLocation())