815 lines
21 KiB
Go
Executable File
815 lines
21 KiB
Go
Executable File
package utils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Observation struct with json.Number for flexible value handling
|
|
type Observation struct {
|
|
ObsKey map[string]string `json:"ObsKey"`
|
|
ObsValue struct {
|
|
Value json.Number `json:"Value"`
|
|
} `json:"ObsValue"`
|
|
}
|
|
|
|
// ------------------------------------------------------------ card calculation---------------------------------------------------
|
|
type GroupByAndSum struct {
|
|
GroupByAndSum string `json:"groupby_and_sum"`
|
|
GetTop int `json:"get_top"`
|
|
Conversion int `json:"conversion"`
|
|
}
|
|
type CountCalculation struct {
|
|
Key string `json:"key"`
|
|
GetCountOf string `json:"get_count_of"`
|
|
}
|
|
type CardCalculationRequest struct {
|
|
Formula string `json:"formula"`
|
|
RecordCount string `json:"record_count"`
|
|
RecodeIndex string `json:"record_index"`
|
|
RecordKey string `json:"record_key"`
|
|
AdditionalRecordKey string `json:"additional_record_key"`
|
|
NumberFormat string `json:"number_format"`
|
|
Conversion string `json:"conversion"`
|
|
GroupByAndSum GroupByAndSum `json:"groupby_and_sum"`
|
|
CountCalculation CountCalculation `json:"count_calculation"`
|
|
}
|
|
|
|
func CardCalculation(data []Observation, req CardCalculationRequest) (map[string]string, error) {
|
|
// Sort the data in descending order based on TimePeriod
|
|
sort.Slice(data, func(i, j int) bool {
|
|
return data[i].ObsKey["TIME_PERIOD"] > data[j].ObsKey["TIME_PERIOD"]
|
|
})
|
|
|
|
var selectedData []Observation
|
|
if req.RecodeIndex != "" {
|
|
|
|
if req.RecodeIndex == "last" {
|
|
index := len(data)
|
|
if index < 0 || index >= len(data) {
|
|
selectedData = []Observation{}
|
|
} else {
|
|
selectedData = []Observation{data[index]}
|
|
}
|
|
|
|
} else {
|
|
// Convert RecodeIndex to an integer
|
|
index, err := strconv.Atoi(req.RecodeIndex)
|
|
if err != nil || index < 0 || index >= len(data) {
|
|
selectedData = []Observation{}
|
|
} else {
|
|
selectedData = []Observation{data[index]}
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
// Determine the number of records to use
|
|
|
|
if req.RecordCount == "all" {
|
|
if len(data) > 0 {
|
|
selectedData = data
|
|
} else {
|
|
selectedData = []Observation{}
|
|
}
|
|
} else if req.RecordCount == "first_pair" {
|
|
if len(data) >= 2 {
|
|
selectedData = data[:2]
|
|
} else {
|
|
selectedData = []Observation{}
|
|
}
|
|
} else if req.RecordCount == "second_pair" {
|
|
if len(data) >= 3 {
|
|
selectedData = data[1:3]
|
|
} else {
|
|
selectedData = []Observation{}
|
|
}
|
|
} else {
|
|
n := len(data) // array leangth
|
|
var err error
|
|
n, err = strconv.Atoi(req.RecordCount)
|
|
if err != nil || n >= len(data) {
|
|
// return nil, errors.New("invalid record count");
|
|
selectedData = []Observation{}
|
|
} else {
|
|
selectedData = data[:n]
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var result float64
|
|
switch req.Formula {
|
|
case "none":
|
|
result = none(selectedData)
|
|
case "total":
|
|
result = sum(selectedData)
|
|
case "average":
|
|
result = average(selectedData)
|
|
case "highest":
|
|
result = highest(selectedData)
|
|
case "lowest":
|
|
result = lowest(selectedData)
|
|
case "different":
|
|
result = difference(selectedData)
|
|
case "groupby_and_sum":
|
|
customCalculationReq := req.GroupByAndSum
|
|
|
|
var chartCustomCalculationRequest ChartCalculationRequest
|
|
|
|
jsonData, err := json.Marshal(customCalculationReq)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
err = json.Unmarshal(jsonData, &chartCustomCalculationRequest)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
selectedData, err = CalculateGroupedSum(selectedData, chartCustomCalculationRequest)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
if len(selectedData) > 0 {
|
|
value, err := selectedData[0].ObsValue.Value.Float64()
|
|
if err == nil {
|
|
result = value
|
|
} else {
|
|
fmt.Println("Error converting first observation value to float64:", err)
|
|
}
|
|
} else {
|
|
fmt.Println("No observations returned from CalculateGroupedSum")
|
|
}
|
|
case "count_calculation":
|
|
countCalculationReq := req.CountCalculation
|
|
|
|
var cardCountCalculationRequest CountCalculation
|
|
|
|
jsonData, err := json.Marshal(countCalculationReq)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
err = json.Unmarshal(jsonData, &cardCountCalculationRequest)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
selectedData, err = CalculateCount(selectedData, cardCountCalculationRequest)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
|
|
if len(selectedData) > 0 {
|
|
result = float64(len(selectedData))
|
|
selectedData = selectedData[:1]
|
|
} else {
|
|
fmt.Println("No observations returned from CalculateGroupedSum")
|
|
}
|
|
|
|
default:
|
|
return nil, errors.New("invalid formula")
|
|
}
|
|
|
|
convertedValue := applyConversion(result, req.Conversion, req.NumberFormat)
|
|
|
|
displayValue := extractRecordKeyValue(selectedData, req.RecordKey, req.AdditionalRecordKey, req.Formula, result)
|
|
|
|
color := ""
|
|
if req.Formula == "different" || req.Conversion == "%" {
|
|
// Remove the percentage sign if present
|
|
convertedValueStr := strings.TrimSuffix(convertedValue, "%")
|
|
|
|
// Convert to float
|
|
convertedValue, err := strconv.ParseFloat(convertedValueStr, 64)
|
|
if err != nil {
|
|
return nil, err // Handle error if conversion fails
|
|
}
|
|
if convertedValue > 0 {
|
|
color = "#11AF22"
|
|
} else if convertedValue < 0 {
|
|
color = "#D83731"
|
|
}
|
|
}
|
|
|
|
return map[string]string{
|
|
"value": convertedValue,
|
|
"display_value": displayValue,
|
|
"calculation": req.Formula,
|
|
"font_color": color,
|
|
}, nil
|
|
}
|
|
|
|
// Helper function to convert json.Number to float64 safely
|
|
func getFloatValue(num json.Number) float64 {
|
|
val, err := num.Float64()
|
|
if err != nil {
|
|
fmt.Println("Error converting value:", err)
|
|
return 0
|
|
}
|
|
return val
|
|
}
|
|
|
|
func none(data []Observation) float64 {
|
|
total := 0.0
|
|
for _, obs := range data {
|
|
total += getFloatValue(obs.ObsValue.Value)
|
|
}
|
|
return total
|
|
}
|
|
|
|
func sum(data []Observation) float64 {
|
|
total := 0.0
|
|
for _, obs := range data {
|
|
total += getFloatValue(obs.ObsValue.Value)
|
|
}
|
|
return total
|
|
}
|
|
|
|
func average(data []Observation) float64 {
|
|
if len(data) == 0 {
|
|
return 0
|
|
}
|
|
return sum(data) / float64(len(data))
|
|
}
|
|
|
|
func highest(data []Observation) float64 {
|
|
if len(data) == 0 {
|
|
return 0
|
|
}
|
|
|
|
max := getFloatValue(data[0].ObsValue.Value)
|
|
for _, obs := range data[1:] {
|
|
val := getFloatValue(obs.ObsValue.Value)
|
|
if val > max {
|
|
max = val
|
|
}
|
|
}
|
|
return max
|
|
|
|
}
|
|
|
|
func lowest(data []Observation) float64 {
|
|
if len(data) == 0 {
|
|
return 0
|
|
}
|
|
|
|
min := getFloatValue(data[0].ObsValue.Value)
|
|
for _, obs := range data {
|
|
val := getFloatValue(obs.ObsValue.Value)
|
|
if val < min {
|
|
min = val
|
|
}
|
|
}
|
|
return min
|
|
}
|
|
|
|
func difference(data []Observation) float64 {
|
|
if len(data) < 2 {
|
|
return 0
|
|
}
|
|
latest := getFloatValue(data[0].ObsValue.Value)
|
|
previous := getFloatValue(data[1].ObsValue.Value)
|
|
if previous == 0 {
|
|
return 0
|
|
}
|
|
return ((latest - previous) / previous) * 100
|
|
}
|
|
|
|
// Function to add commas to a number string
|
|
func addCommas(num string, suffix string) string {
|
|
n := len(num)
|
|
if n <= 3 {
|
|
return num + suffix // Attach suffix directly if no commas needed
|
|
}
|
|
var sb strings.Builder
|
|
start := n % 3
|
|
if start == 0 {
|
|
start = 3
|
|
}
|
|
sb.WriteString(num[:start])
|
|
|
|
for i := start; i < n; i += 3 {
|
|
sb.WriteString("," + num[i:i+3])
|
|
}
|
|
return sb.String() + suffix
|
|
}
|
|
|
|
func formatNumberWithCommas(value float64, suffix string) string {
|
|
// Convert the value to a string with 2 decimal places
|
|
formatted := fmt.Sprintf("%.2f", value)
|
|
|
|
// Split integer and decimal parts
|
|
parts := strings.Split(formatted, ".")
|
|
integerPart := parts[0]
|
|
decimalPart := parts[1]
|
|
|
|
// Add commas to the integer part
|
|
var result strings.Builder
|
|
n := len(integerPart)
|
|
for i, c := range integerPart {
|
|
if i > 0 && (n-i)%3 == 0 {
|
|
result.WriteRune(',')
|
|
}
|
|
result.WriteRune(c)
|
|
}
|
|
|
|
// Append decimal part if needed
|
|
if decimalPart != "00" {
|
|
return result.String() + "." + decimalPart + suffix
|
|
}
|
|
return result.String() + suffix
|
|
}
|
|
|
|
func applyConversion(value float64, conversion string, numberFormat string) string {
|
|
if numberFormat == "T" {
|
|
value *= 1000000000000
|
|
} else if numberFormat == "B" {
|
|
value *= 1000000000
|
|
} else if numberFormat == "M" {
|
|
value *= 1000000
|
|
} else if numberFormat == "K" {
|
|
value *= 1000
|
|
}
|
|
|
|
// Helper function to format numbers with a thousands separator only in the default case
|
|
formatNumber := func(v float64, suffix string) string {
|
|
if suffix == "" || suffix == "K" || suffix == "M" || suffix == "B" || suffix == "T" {
|
|
if v == float64(int(v)) { // No decimal part
|
|
return addCommas(strconv.Itoa(int(v)), suffix)
|
|
}
|
|
return formatNumberWithCommas(v, suffix)
|
|
}
|
|
if v == float64(int(v)) { // No decimal part
|
|
return fmt.Sprintf("%d%s", int(v), suffix)
|
|
}
|
|
return fmt.Sprintf("%.2f%s", v, suffix)
|
|
}
|
|
|
|
switch conversion {
|
|
case "T":
|
|
return formatNumber(value/1000000000000, "T")
|
|
case "B":
|
|
return formatNumber(value/1000000000, "B")
|
|
case "M":
|
|
return formatNumber(value/1000000, "M")
|
|
case "K":
|
|
return formatNumber(value/1000, "K")
|
|
case "%":
|
|
return formatNumber(value, "%")
|
|
default:
|
|
return formatNumber(value, conversion)
|
|
}
|
|
}
|
|
|
|
func extractRecordKeyValue(data []Observation, key string, additionalkey string, formula string, targetValue float64) string {
|
|
if len(data) == 0 {
|
|
return ""
|
|
}
|
|
|
|
switch formula {
|
|
case "highest", "lowest", "none", "groupby_and_sum":
|
|
for _, obs := range data {
|
|
value, err := obs.ObsValue.Value.Float64()
|
|
if err == nil && value == targetValue {
|
|
if val, exists := obs.ObsKey[key]; exists {
|
|
|
|
// Check if additionalkey exists in obs.ObsKey
|
|
if val2, exists2 := obs.ObsKey[additionalkey]; exists2 {
|
|
|
|
return val + " - " + val2
|
|
} else {
|
|
return val
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case "total", "average", "different":
|
|
if val1, exists1 := data[0].ObsKey[key]; exists1 {
|
|
if val2, exists2 := data[len(data)-1].ObsKey[key]; exists2 {
|
|
if val1 == val2 {
|
|
return val1
|
|
} else {
|
|
if val1 > val2 {
|
|
return val2 + " - " + val1
|
|
} else {
|
|
return val1 + " - " + val2
|
|
}
|
|
}
|
|
}
|
|
return val1
|
|
}
|
|
case "count_calculation":
|
|
for _, obs := range data {
|
|
|
|
if val, exists := obs.ObsKey[key]; exists {
|
|
|
|
// Check if additionalkey exists in obs.ObsKey
|
|
if val2, exists2 := obs.ObsKey[additionalkey]; exists2 {
|
|
|
|
return val + " - " + val2
|
|
} else {
|
|
return val
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// ------------------------------------------------------------ chart calculation---------------------------------------------------
|
|
type ChartCalculationRequest struct {
|
|
GroupByAndSum string `json:"groupby_and_sum"`
|
|
AdditionalForGroupByAndSum string `json:"additional_for_groupby_and_sum"`
|
|
GetTop string `json:"get_top"`
|
|
SortBy string `json:"sort_by"`
|
|
SortAction string `json:"sort_action"`
|
|
Conversion string `json:"conversion"`
|
|
}
|
|
|
|
func CalculateGroupedSum(observations []Observation, request ChartCalculationRequest) ([]Observation, error) {
|
|
// Map to store the summed values
|
|
summedData := make(map[string]float64)
|
|
|
|
if request.GroupByAndSum == "none" {
|
|
// If "none", treat each observation separately
|
|
|
|
// Collect observations with their original keys
|
|
var valueList []Observation
|
|
|
|
for _, obs := range observations {
|
|
value, err := obs.ObsValue.Value.Float64()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid number format: %v", err)
|
|
}
|
|
|
|
// Preserve the original ObsKey
|
|
valueList = append(valueList, Observation{
|
|
ObsKey: obs.ObsKey, // Keep all keys
|
|
ObsValue: struct {
|
|
Value json.Number `json:"Value"`
|
|
}{
|
|
Value: json.Number(strconv.FormatFloat(convertValue(value, request.Conversion), 'f', 6, 64)),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Sort by value in descending order or given key
|
|
sortByKey := request.SortBy
|
|
if sortByKey != "" {
|
|
// Sort by given key
|
|
sort.Slice(valueList, func(i, j int) bool {
|
|
val1Str := valueList[i].ObsKey[sortByKey]
|
|
val2Str := valueList[j].ObsKey[sortByKey]
|
|
|
|
val1, _ := strconv.ParseFloat(val1Str, 64)
|
|
val2, _ := strconv.ParseFloat(val2Str, 64)
|
|
|
|
sortActionKey := request.SortAction
|
|
if sortActionKey == "DESC" {
|
|
return val1 > val2 // Descending order
|
|
}
|
|
return val1 < val2 // Ascending order
|
|
|
|
})
|
|
} else {
|
|
// Default sorting by ObsValue.Value
|
|
sort.Slice(valueList, func(i, j int) bool {
|
|
val1, _ := valueList[i].ObsValue.Value.Float64()
|
|
val2, _ := valueList[j].ObsValue.Value.Float64()
|
|
return val1 > val2 // Descending order
|
|
})
|
|
}
|
|
|
|
// Get the top N values
|
|
topN, err := strconv.Atoi(request.GetTop)
|
|
if err != nil || topN <= 0 || topN > len(valueList) {
|
|
topN = len(valueList) // If invalid input, return all
|
|
}
|
|
|
|
return valueList[:topN], nil
|
|
|
|
} else {
|
|
// Iterate through observations and sum values based on GroupBy key and Additional key
|
|
for _, obs := range observations {
|
|
groupKey, exists := obs.ObsKey[request.GroupByAndSum]
|
|
if !exists {
|
|
continue // Skip if the key does not exist in ObsKey
|
|
}
|
|
|
|
additionalKey := ""
|
|
if request.AdditionalForGroupByAndSum != "" {
|
|
additionalKey, _ = obs.ObsKey[request.AdditionalForGroupByAndSum]
|
|
}
|
|
|
|
combinedKey := groupKey
|
|
if additionalKey != "" {
|
|
combinedKey += "_" + additionalKey
|
|
}
|
|
|
|
value, err := obs.ObsValue.Value.Float64()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid number format: %v", err)
|
|
}
|
|
|
|
summedData[combinedKey] += value
|
|
}
|
|
}
|
|
|
|
// Convert map to slice of Observation
|
|
var result []Observation
|
|
for key, sum := range summedData {
|
|
keyParts := strings.Split(key, "_")
|
|
obsKey := map[string]string{
|
|
request.GroupByAndSum: keyParts[0],
|
|
}
|
|
if len(keyParts) > 1 {
|
|
obsKey[request.AdditionalForGroupByAndSum] = keyParts[1]
|
|
}
|
|
|
|
result = append(result, Observation{
|
|
ObsKey: obsKey,
|
|
ObsValue: struct {
|
|
Value json.Number `json:"Value"`
|
|
}{
|
|
Value: json.Number(strconv.FormatFloat(convertValue(sum, request.Conversion), 'f', 6, 64)),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Sort by value in descending order or given key
|
|
sortByKey := request.SortBy
|
|
if sortByKey != "" {
|
|
// Sort by given key
|
|
sort.Slice(result, func(i, j int) bool {
|
|
val1Str := result[i].ObsKey[sortByKey]
|
|
val2Str := result[j].ObsKey[sortByKey]
|
|
|
|
val1, _ := strconv.ParseFloat(val1Str, 64)
|
|
val2, _ := strconv.ParseFloat(val2Str, 64)
|
|
|
|
sortActionKey := request.SortAction
|
|
if sortActionKey == "DESC" {
|
|
return val1 > val2 // Descending order
|
|
}
|
|
return val1 < val2 // Ascending order
|
|
})
|
|
} else {
|
|
// Default sorting by ObsValue.Value
|
|
sort.Slice(result, func(i, j int) bool {
|
|
val1, _ := result[i].ObsValue.Value.Float64()
|
|
val2, _ := result[j].ObsValue.Value.Float64()
|
|
return val1 > val2 // Descending order
|
|
})
|
|
}
|
|
// Sort by summed values in descending order
|
|
// sort.Slice(result, func(i, j int) bool {
|
|
// val1, _ := result[i].ObsValue.Value.Float64()
|
|
// val2, _ := result[j].ObsValue.Value.Float64()
|
|
// return val1 > val2 // Sort descending
|
|
// })
|
|
|
|
// Get the top N values
|
|
topN, err := strconv.Atoi(request.GetTop)
|
|
if err != nil || topN <= 0 || topN > len(result) {
|
|
topN = len(result) // If invalid input, return all
|
|
}
|
|
|
|
return result[:topN], nil
|
|
}
|
|
|
|
// CalculateGroupedSum processes the observations based on the given request
|
|
// func CalculateGroupedSum(observations []Observation, request ChartCalculationRequest) ([]Observation, error) {
|
|
|
|
// // Map to store the summed values
|
|
// summedData := make(map[string]float64)
|
|
|
|
// if request.GroupByAndSum == "none" {
|
|
// // If "none", treat each observation separately
|
|
// for _, obs := range observations {
|
|
// value, err := obs.ObsValue.Value.Float64()
|
|
// if err != nil {
|
|
// return nil, fmt.Errorf("invalid number format: %v", err)
|
|
// }
|
|
// // Use a unique key (e.g., index-based) to preserve order
|
|
// summedData[strconv.Itoa(len(summedData))] = value
|
|
// }
|
|
// } else {
|
|
// // Iterate through observations and sum values based on GroupBy key
|
|
// for _, obs := range observations {
|
|
// groupKey, exists := obs.ObsKey[request.GroupByAndSum]
|
|
// if !exists {
|
|
// continue // Skip if the key does not exist in ObsKey
|
|
// }
|
|
|
|
// value, err := obs.ObsValue.Value.Float64()
|
|
// if err != nil {
|
|
// return nil, fmt.Errorf("invalid number format: %v", err)
|
|
// }
|
|
|
|
// summedData[groupKey] += value
|
|
// }
|
|
|
|
// }
|
|
|
|
// // Convert map to slice of Observation
|
|
// var result []Observation
|
|
// for key, sum := range summedData {
|
|
// result = append(result, Observation{
|
|
// ObsKey: map[string]string{
|
|
// request.GroupByAndSum: key,
|
|
// },
|
|
// ObsValue: struct {
|
|
// Value json.Number `json:"Value"`
|
|
// }{
|
|
// // Value: json.Number(fmt.Sprintf("%.0f", sum)),
|
|
// Value: json.Number(strconv.FormatFloat(convertValue(sum, request.Conversion), 'f', 6, 64)),
|
|
// },
|
|
// })
|
|
// }
|
|
|
|
// // Sort by summed values in descending order
|
|
// sort.Slice(result, func(i, j int) bool {
|
|
// val1, _ := result[i].ObsValue.Value.Float64()
|
|
// val2, _ := result[j].ObsValue.Value.Float64()
|
|
// return val1 > val2 // Sort descending
|
|
// })
|
|
|
|
// // Get the top N values
|
|
// topN, err := strconv.Atoi(request.GetTop)
|
|
// if err != nil || topN <= 0 || topN > len(result) {
|
|
// topN = len(result) // If invalid input, return all
|
|
// }
|
|
|
|
// return result[:topN], nil
|
|
// }
|
|
|
|
// Convert values based on conversion type
|
|
func convertValue(value float64, conversion string) float64 {
|
|
switch conversion {
|
|
case "T": // Trillion
|
|
return round(value/1000000000000, 2)
|
|
case "B": // Billion
|
|
return round(value/1000000000, 2)
|
|
case "M": // Million
|
|
return round(value/1000000, 2)
|
|
case "K": // Thousand
|
|
return round(value/1000, 2)
|
|
default: // No conversion
|
|
return round(value, 2)
|
|
}
|
|
}
|
|
|
|
// Function to round a float to 2 decimal places
|
|
func round(val float64, precision int) float64 {
|
|
p := math.Pow(10, float64(precision))
|
|
return math.Round(val*p) / p
|
|
}
|
|
|
|
//------------------------------------------------------------- default filter ---------------------------------------------------
|
|
|
|
func DefaultYearFilter(data []Observation, latestYearCount string) ([]Observation, error) {
|
|
// Convert latestYearCount to integer
|
|
yearCount, err := strconv.Atoi(latestYearCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if yearCount == 0 {
|
|
yearCount = 1
|
|
}
|
|
|
|
// Sort the data in descending order based on TIME_PERIOD
|
|
sort.Slice(data, func(i, j int) bool {
|
|
return data[i].ObsKey["TIME_PERIOD"] > data[j].ObsKey["TIME_PERIOD"]
|
|
})
|
|
|
|
// Collect unique years in sorted order
|
|
uniqueYears := make([]string, 0)
|
|
yearMap := make(map[string]bool)
|
|
|
|
for _, item := range data {
|
|
year := item.ObsKey["TIME_PERIOD"]
|
|
if !yearMap[year] {
|
|
yearMap[year] = true
|
|
uniqueYears = append(uniqueYears, year)
|
|
}
|
|
}
|
|
|
|
// Adjust year count if there are fewer available years
|
|
if len(uniqueYears) < yearCount {
|
|
yearCount = len(uniqueYears) // Set to available years
|
|
}
|
|
|
|
// Create a set for only the required latest years
|
|
latestYears := make(map[string]bool)
|
|
for i := 0; i < yearCount; i++ {
|
|
latestYears[uniqueYears[i]] = true
|
|
}
|
|
|
|
// Filter the data to keep only records from the selected latest N years
|
|
var filteredData []Observation
|
|
for _, item := range data {
|
|
if latestYears[item.ObsKey["TIME_PERIOD"]] {
|
|
filteredData = append(filteredData, item)
|
|
}
|
|
}
|
|
|
|
return filteredData, nil
|
|
}
|
|
|
|
type Filter struct {
|
|
FilterKey string `json:"filter_key"`
|
|
FilterData []string `json:"filter_data"`
|
|
}
|
|
|
|
func FilterByMultipleKeys(data []Observation, filters []Filter) ([]Observation, error) {
|
|
var filteredData []Observation
|
|
|
|
// Convert filter conditions into a map for quick lookup
|
|
filterMap := make(map[string]map[string]bool)
|
|
for _, filter := range filters {
|
|
if len(filter.FilterData) == 0 {
|
|
continue // Skip empty filter_data
|
|
}
|
|
if filterMap[filter.FilterKey] == nil {
|
|
filterMap[filter.FilterKey] = make(map[string]bool)
|
|
}
|
|
for _, value := range filter.FilterData {
|
|
filterMap[filter.FilterKey][value] = true
|
|
}
|
|
}
|
|
fmt.Println("filterMap :", filterMap)
|
|
// Iterate through data and apply the filters
|
|
for _, item := range data {
|
|
matches := true
|
|
for key, allowedValues := range filterMap {
|
|
if _, exists := item.ObsKey[key]; exists {
|
|
if !allowedValues[item.ObsKey[key]] { // If value not in allowed list
|
|
matches = false
|
|
break
|
|
}
|
|
} else {
|
|
matches = false // Key not present in data
|
|
break
|
|
}
|
|
}
|
|
|
|
if matches {
|
|
filteredData = append(filteredData, item)
|
|
}
|
|
}
|
|
|
|
return filteredData, nil
|
|
}
|
|
|
|
//-------------------------------------------------------------- Count Calculation -------------------------------------------------
|
|
|
|
func CalculateCount(observations []Observation, request CountCalculation) ([]Observation, error) {
|
|
|
|
if request.GetCountOf == "highest" || request.GetCountOf == "lowest" {
|
|
var extremeValue int
|
|
var filteredObservations []Observation
|
|
first := true
|
|
isHighest := request.GetCountOf == "highest"
|
|
|
|
for _, obs := range observations {
|
|
if valStr, exists := obs.ObsKey[request.Key]; exists {
|
|
val, err := strconv.Atoi(valStr)
|
|
if err != nil {
|
|
continue // Skip if conversion fails
|
|
}
|
|
|
|
if first || (isHighest && val > extremeValue) || (!isHighest && val < extremeValue) {
|
|
extremeValue = val
|
|
first = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter observations that match the extreme value
|
|
for _, obs := range observations {
|
|
if valStr, exists := obs.ObsKey[request.Key]; exists {
|
|
val, err := strconv.Atoi(valStr)
|
|
if err == nil && val == extremeValue {
|
|
filteredObservations = append(filteredObservations, obs)
|
|
}
|
|
}
|
|
}
|
|
|
|
return filteredObservations, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unsupported get_count_of value: %s", request.GetCountOf)
|
|
}
|