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"` 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 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 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) }