diff --git a/engine/rateprofile.go b/engine/rateprofile.go
index 6e5fd7f2d..2a7cb2f50 100644
--- a/engine/rateprofile.go
+++ b/engine/rateprofile.go
@@ -45,8 +45,20 @@ type RateProfile struct {
maxCost *utils.Decimal
}
-func (rpp *RateProfile) TenantID() string {
- return utils.ConcatenatedKey(rpp.Tenant, rpp.ID)
+func (rp *RateProfile) TenantID() string {
+ return utils.ConcatenatedKey(rp.Tenant, rp.ID)
+}
+
+func (rp *RateProfile) Compile() (err error) {
+ rp.connFee = utils.NewDecimalFromFloat64(rp.ConnectFee)
+ rp.minCost = utils.NewDecimalFromFloat64(rp.MinCost)
+ rp.minCost = utils.NewDecimalFromFloat64(rp.MaxCost)
+ for _, rtP := range rp.Rates {
+ if err = rtP.Compile(); err != nil {
+ return
+ }
+ }
+ return
}
// Route defines rate related information used within a RateProfile
@@ -61,6 +73,21 @@ type Rate struct {
aTime cron.Schedule // compiled version of activation time as cron.Schedule interface
}
+func (rt *Rate) Compile() (err error) {
+ aTime := rt.ActivationStart
+ if aTime == utils.EmptyString {
+ aTime = "* * * * *"
+ }
+ if rt.aTime, err = cron.ParseStandard(aTime); err != nil {
+ return
+ }
+ return
+}
+
+func (rt *Rate) NextActivationTime(t time.Time) time.Time {
+ return rt.aTime.Next(t)
+}
+
type IntervalRate struct {
IntervalStart time.Duration // Starting point when the Rate kicks in
Unit time.Duration // RateUnit
@@ -83,6 +110,7 @@ type RateSInterval struct {
}
type RateSIncrement struct {
- UsageStart time.Duration
- Rate *Rate
+ Rate *Rate
+ IntervalRateIndex int
+ Usage time.Duration
}
diff --git a/rates/librates.go b/rates/librates.go
index 4e6e81a70..4f267a052 100644
--- a/rates/librates.go
+++ b/rates/librates.go
@@ -18,46 +18,27 @@ along with this program. If not, see
package rates
-/*
+import (
+ "sort"
+ "time"
+
+ "github.com/cgrates/cgrates/engine"
+)
+
// orderRatesOnIntervals will order the rates based on ActivationInterval and intervalStart of each Rate
// there can be only one winning Rate for each interval, prioritized by the Weight
-func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, isDuration bool) (ordRts []*engine.RateSInterval) {
+func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, usage time.Duration, isDuration bool) (ordRts []*engine.RateSInterval) {
+ cronSTime := sTime.Add(time.Duration(-1 * time.Minute)) // cron min verbosity is minute
+ endTime := sTime.Add(usage) // cover also the last unit used
// index the received rates
- rtIdx := make(map[time.Time]map[time.Duration][]*engine.Rate) // map[ActivationTime]map[IntervalStart][]*engine.Rate
+ rtIdx := make(map[time.Time][]*engine.Rate) // map[ActivationTime][]*engine.Rate
for _, rt := range aRts {
- var rtATimesKey time.Time
- if rt.ActivationInterval != nil {
- rtATimesKey = rt.ActivationInterval.ActivationTime
+ nextRunTime := rt.NextActivationTime(cronSTime)
+ if nextRunTime.After(endTime) {
+ continue
}
- if _, has := rtIdx[rtATimesKey]; !has {
- rtIdx[rtATimesKey] = make(map[time.Duration][]*engine.Rate)
- }
- rtIdx[rtATimesKey][rt.IntervalStart] = append(rtIdx[rtATimesKey][rt.IntervalStart], rt)
+ rtIdx[nextRunTime] = append(rtIdx[nextRunTime], rt)
}
-
- // sort the rates within the duration map
- for _, durMp := range rtIdx {
- for _, rts := range durMp {
- sort.Slice(rts, func(i, j int) bool {
- return rts[i].Weight > rts[j].Weight
- })
- }
- }
-
- // sort the IntervalStarts
- sortedStarts := make(map[time.Time][]time.Duration) // map[ActivationTime]
- for aTime, durMp := range rtIdx {
- sortedStarts[aTime] = make([]time.Duration, len(durMp))
- idxDur := 0
- for dur := range durMp {
- sortedStarts[aTime] = append(sortedStarts[aTime], dur)
- idxDur++
- }
- sort.Slice(sortedStarts[aTime], func(i, j int) bool {
- return sortedStarts[aTime][i] < sortedStarts[aTime][j]
- })
- }
-
// sort the activation times
sortedATimes := make([]time.Time, len(rtIdx))
idxATimes := 0
@@ -68,7 +49,49 @@ func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, isDuration bool
sort.Slice(sortedATimes, func(i, j int) bool {
return sortedATimes[i].Before(sortedATimes[j])
})
+ // start with most recent activationTime lower or equal to sTime
+ for i, aT := range sortedATimes {
+ if !aT.After(sTime) || i == 0 {
+ continue
+ }
+ sortedATimes = sortedATimes[i-1:]
+ }
+ // only for duration we will have multiple activationTimes
+ if !isDuration {
+ sortedATimes = sortedATimes[:1]
+ }
+ var usageSIdx, usageEIdx time.Duration
+ for i, at := range sortedATimes {
+ if sTime.Before(at) {
+ usageSIdx = at.Sub(sTime)
+ }
+ if i != len(sortedATimes)-1 { // not the last one
+ usageEIdx = sortedATimes[i+1].Sub(sTime)
+ } else {
+ usageEIdx = usage
+ }
+ // Sort the rates based on their Weight
+ sort.Slice(rtIdx[at], func(i, j int) bool {
+ return rtIdx[at][i].Weight < rtIdx[at][j].Weight
+ })
+ var rtIcmts []*engine.RateSIncrement
+ for _, rt := range rtIdx[at] {
+ for j, ivlRt := range rt.IntervalRates {
+ if ivlRt.IntervalStart > usageEIdx {
+ break
+ }
+ if j != len(rt.IntervalRates)-1 &&
+ rt.IntervalRates[j+1].IntervalStart <= usageSIdx { // not the last one
+ // the next intervalStat is still good for usageSIdx, no need of adding the current one
+ continue
+ }
+ // ready to add the increment
+ rtIcmts = append(rtIcmts,
+ &engine.RateSIncrement{Rate: rt, IntervalRateIndex: j})
+ }
+ }
+ ordRts = append(ordRts, &engine.RateSInterval{Increments: rtIcmts})
+ }
return
}
-*/
diff --git a/rates/librates_test.go b/rates/librates_test.go
index 465dae10d..ca342f282 100644
--- a/rates/librates_test.go
+++ b/rates/librates_test.go
@@ -18,138 +18,54 @@ along with this program. If not, see
package rates
-/*
+import (
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
func TestOrderRatesOnIntervals(t *testing.T) {
- allRts := map[time.Duration][]*engine.Rate{
- time.Duration(0): {
- &engine.Rate{
- ID: "RATE0",
+ rt0 := &engine.Rate{
+ ID: "RATE0",
+ Weight: 0,
+ IntervalRates: []*engine.IntervalRate{
+ {
IntervalStart: time.Duration(0),
},
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- &engine.Rate{
- ID: "RATE50",
- IntervalStart: time.Duration(0),
- Weight: 50,
- },
},
}
- expOrdered := []*engine.Rate{
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- }
- if ordRts := orderRatesOnIntervals(allRts); !reflect.DeepEqual(expOrdered, ordRts) {
- t.Errorf("expecting: %s\n, received: %s",
- utils.ToIJSON(expOrdered), utils.ToIJSON(ordRts))
- }
- allRts = map[time.Duration][]*engine.Rate{
- time.Duration(1 * time.Minute): {
- &engine.Rate{
- ID: "RATE30",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 30,
- },
- &engine.Rate{
- ID: "RATE70",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 70,
- },
- },
- time.Duration(0): {
- &engine.Rate{
- ID: "RATE0",
+ rt0.Compile()
+ rtChristmas := &engine.Rate{
+ ID: "RT_CHRISTMAS",
+ ActivationStart: "* * 24 12 *",
+ Weight: 50,
+ IntervalRates: []*engine.IntervalRate{
+ {
IntervalStart: time.Duration(0),
},
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- &engine.Rate{
- ID: "RATE50",
- IntervalStart: time.Duration(0),
- Weight: 50,
+ },
+ }
+ rtChristmas.Compile()
+ allRts := []*engine.Rate{rt0, rtChristmas}
+ expOrdered := []*engine.RateSInterval{
+ {
+ Increments: []*engine.RateSIncrement{
+ {
+ Rate: rt0,
+ IntervalRateIndex: 0,
+ },
},
},
}
- expOrdered = []*engine.Rate{
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- &engine.Rate{
- ID: "RATE70",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 70,
- },
- }
- if ordRts := orderRatesOnIntervals(allRts); !reflect.DeepEqual(expOrdered, ordRts) {
- t.Errorf("expecting: %s\n, received: %s",
- utils.ToIJSON(expOrdered), utils.ToIJSON(ordRts))
- }
- allRts = map[time.Duration][]*engine.Rate{
- time.Duration(1 * time.Minute): {
- &engine.Rate{
- ID: "RATE30",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 30,
- },
- &engine.Rate{
- ID: "RATE70",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 70,
- },
- },
- time.Duration(2 * time.Minute): {
- &engine.Rate{
- ID: "RATE0",
- IntervalStart: time.Duration(2 * time.Minute),
- },
- },
- time.Duration(0): {
- &engine.Rate{
- ID: "RATE0",
- IntervalStart: time.Duration(0),
- },
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- &engine.Rate{
- ID: "RATE50",
- IntervalStart: time.Duration(0),
- Weight: 50,
- },
- },
- }
- expOrdered = []*engine.Rate{
- &engine.Rate{
- ID: "RATE100",
- IntervalStart: time.Duration(0),
- Weight: 100,
- },
- &engine.Rate{
- ID: "RATE70",
- IntervalStart: time.Duration(1 * time.Minute),
- Weight: 70,
- },
- &engine.Rate{
- ID: "RATE0",
- IntervalStart: time.Duration(2 * time.Minute),
- },
- }
- if ordRts := orderRatesOnIntervals(allRts); !reflect.DeepEqual(expOrdered, ordRts) {
+ //
+ // time.Date(2020, time.December, 23, 23, 59, 05, 0, time.UTC),
+ if ordRts := orderRatesOnIntervals(
+ allRts, time.Date(2020, time.June, 28, 18, 56, 05, 0, time.UTC),
+ time.Duration(2*time.Minute), true); !reflect.DeepEqual(expOrdered, ordRts) {
t.Errorf("expecting: %s\n, received: %s",
utils.ToIJSON(expOrdered), utils.ToIJSON(ordRts))
}
}
-*/