RateS with initial OrderRatesOnIntervals

This commit is contained in:
DanB
2020-07-02 18:37:56 +02:00
parent 127f6ee130
commit f9cc4b4db5
3 changed files with 127 additions and 160 deletions

View File

@@ -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
}

View File

@@ -18,46 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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
}
*/

View File

@@ -18,138 +18,54 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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))
}
}
*/