Files
hfb_price_tool/internal/pricing/config.go
T
2026-10-25 15:02:41 +08:00

207 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package pricing
import (
"math"
"regexp"
"sort"
"strconv"
"strings"
)
var (
fractionPricePattern = regexp.MustCompile(`(\d+(?:\.\d+)?)\s*元\s*/\s*(\d+(?:\.\d+)?)`)
singlePricePattern = regexp.MustCompile(`(\d+(?:\.\d+)?)\s*元`)
)
// Normalize 保证配置始终可计算,并修补线上同步/手工编辑产生的空字段。
func Normalize(config Config) Config {
defaults := DefaultConfig()
config.SchemaVersion = 1
insurances := make([]InsuranceBaseRatio, 0, len(config.InsuranceBaseRatios))
seenInsurance := make(map[string]struct{}, len(config.InsuranceBaseRatios))
for _, item := range config.InsuranceBaseRatios {
item.Insurance = strings.TrimSpace(item.Insurance)
if item.Insurance == "" || !isFinitePositive(item.Ratio) {
continue
}
if _, exists := seenInsurance[item.Insurance]; exists {
continue
}
seenInsurance[item.Insurance] = struct{}{}
item.Ratio = RoundRatio(item.Ratio)
insurances = append(insurances, item)
}
if len(insurances) == 0 {
insurances = defaults.InsuranceBaseRatios
}
config.InsuranceBaseRatios = insurances
ratioItems := make([]RatioConfigItem, 0, len(config.RatioConfigItems))
seenRatioKey := make(map[string]struct{}, len(config.RatioConfigItems))
for _, item := range config.RatioConfigItems {
item.Key = strings.TrimSpace(item.Key)
item.Label = strings.TrimSpace(item.Label)
item.Kind = strings.TrimSpace(item.Kind)
item.GroupKey = strings.TrimSpace(item.GroupKey)
if item.Key == "" || item.Label == "" || !isFiniteNonNegative(item.MissingPenalty) {
continue
}
if _, exists := seenRatioKey[item.Key]; exists {
continue
}
seenRatioKey[item.Key] = struct{}{}
configKind := item.Kind
if configKind != "skin_group" && configKind != "max_stamina" && configKind != "max_load" {
configKind = "skin_group"
}
item.Kind = configKind
item.MissingPenalty = RoundRatio(item.MissingPenalty)
ratioItems = append(ratioItems, item)
}
if len(ratioItems) == 0 {
ratioItems = defaults.RatioConfigItems
}
config.RatioConfigItems = ratioItems
corrections := make([]CoinCorrection, 0, len(config.CoinCorrections))
for _, item := range config.CoinCorrections {
if !isFiniteNonNegative(item.ThresholdM) || !isFiniteNonNegative(item.Correction) {
continue
}
item.ThresholdM = RoundRatio(item.ThresholdM)
item.Correction = RoundRatio(item.Correction)
corrections = append(corrections, item)
}
if len(corrections) == 0 {
corrections = defaults.CoinCorrections
}
sort.SliceStable(corrections, func(i, j int) bool {
return corrections[i].ThresholdM < corrections[j].ThresholdM
})
config.CoinCorrections = corrections
quantityItems := make([]QuantityItem, 0, len(config.QuantityItems))
seenQuantityKey := make(map[string]struct{}, len(config.QuantityItems))
for _, item := range config.QuantityItems {
item.Key = strings.TrimSpace(item.Key)
item.Label = strings.TrimSpace(item.Label)
item.Price = strings.TrimSpace(item.Price)
item.Unit = strings.TrimSpace(item.Unit)
if item.Key == "" || item.Label == "" || item.Price == "" {
continue
}
if _, exists := seenQuantityKey[item.Key]; exists {
continue
}
seenQuantityKey[item.Key] = struct{}{}
if item.Unit == "" {
item.Unit = defaultUnit(item.Key, item.Label)
}
quantityItems = append(quantityItems, item)
}
if len(quantityItems) == 0 {
quantityItems = defaults.QuantityItems
}
config.QuantityItems = quantityItems
lossOptions := make([]float64, 0, len(config.DailyLossOptions))
seenLoss := make(map[float64]struct{}, len(config.DailyLossOptions))
for _, value := range config.DailyLossOptions {
if !isFinitePositive(value) {
continue
}
value = RoundRatio(value)
if _, exists := seenLoss[value]; exists {
continue
}
seenLoss[value] = struct{}{}
lossOptions = append(lossOptions, value)
}
if len(lossOptions) == 0 {
lossOptions = defaults.DailyLossOptions
}
sort.Float64s(lossOptions)
config.DailyLossOptions = lossOptions
if !isFinitePositive(config.AcceleratedMaxRatio) {
config.AcceleratedMaxRatio = defaults.AcceleratedMaxRatio
}
config.AcceleratedMaxRatio = math.Min(RoundRatio(config.AcceleratedMaxRatio), 50)
config.SyncURL = strings.TrimRight(strings.TrimSpace(config.SyncURL), "/")
if config.SyncURL == "" {
config.SyncURL = defaults.SyncURL
}
return config
}
// ParseUnitPrice 解析「0.6元/发」「6元/60发」这类单价文本,返回单个物品的价格。
func ParseUnitPrice(price string) float64 {
normalized := strings.ReplaceAll(strings.TrimSpace(price), "/", "/")
normalized = strings.ReplaceAll(normalized, ",", ",")
if match := fractionPricePattern.FindStringSubmatch(normalized); len(match) == 3 {
amount := parseFloat(match[1])
count := parseFloat(match[2])
if amount > 0 && count > 0 {
return amount / count
}
}
if match := singlePricePattern.FindStringSubmatch(normalized); len(match) == 2 {
return math.Max(parseFloat(match[1]), 0)
}
return 0
}
func RoundMoney(value float64) float64 {
return math.Round(value*10) / 10
}
func RoundRatio(value float64) float64 {
return math.Round(value*10) / 10
}
func isItemDisabledForInsurance(item QuantityItem, insurance string) bool {
if item.Disabled {
return true
}
if insurance != "3*3" {
return false
}
return item.Key == "gridCard9" ||
strings.Contains(item.Label, "9格") ||
strings.Contains(item.Label, "顶级保险卡")
}
func defaultUnit(key, label string) string {
switch key {
case "awmAmmo", "level6Ammo", "barrettAmmo":
return "发"
case "gridCard9":
return "张"
}
if strings.Contains(label, "子弹") {
return "发"
}
if strings.Contains(label, "卡") {
return "张"
}
return "个"
}
func parseFloat(value string) float64 {
result, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
if err != nil || math.IsNaN(result) || math.IsInf(result, 0) {
return 0
}
return result
}
func isFinitePositive(value float64) bool {
return value > 0 && !math.IsNaN(value) && !math.IsInf(value, 0)
}
func isFiniteNonNegative(value float64) bool {
return value >= 0 && !math.IsNaN(value) && !math.IsInf(value, 0)
}