From 6d3e011634b64a2d06a9ba3aa261abd4ba45f6c0 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 22 Jul 2013 22:15:14 +0300 Subject: [PATCH 1/3] some rating optimization and all hardcoded values using *lowercase --- rater/action.go | 22 +++++++------- rater/activationperiod.go | 6 +++- rater/activationperiod_test.go | 20 ++++++------ rater/callcost_test.go | 18 +++++------ rater/calldesc.go | 24 +++++++-------- rater/calldesc_test.go | 54 ++++++++++++++++----------------- rater/dateseries.go | 16 +++++----- rater/dateseries_test.go | 8 ++--- rater/interval.go | 21 +++++++++++++ rater/loader_csv_test.go | 36 +++++++++++----------- rater/minute_buckets.go | 4 +-- rater/simple_serializer_test.go | 4 +-- rater/timespans.go | 9 ++++++ rater/units_counter_test.go | 2 +- rater/userbalance.go | 28 ++++++++--------- rater/userbalance_test.go | 14 ++++----- 16 files changed, 160 insertions(+), 126 deletions(-) diff --git a/rater/action.go b/rater/action.go index 62aa77f42..99a7c9964 100644 --- a/rater/action.go +++ b/rater/action.go @@ -39,17 +39,17 @@ type Action struct { } const ( - LOG = "LOG" - RESET_TRIGGERS = "RESET_TRIGGERS" - SET_POSTPAID = "SET_POSTPAID" - RESET_POSTPAID = "RESET_POSTPAID" - SET_PREPAID = "SET_PREPAID" - RESET_PREPAID = "RESET_PREPAID" - TOPUP_RESET = "TOPUP_RESET" - TOPUP = "TOPUP" - DEBIT = "DEBIT" - RESET_COUNTER = "RESET_COUNTER" - RESET_COUNTERS = "RESET_COUNTERS" + LOG = "*log" + RESET_TRIGGERS = "*reset_triggers" + SET_POSTPAID = "*set_postpaid" + RESET_POSTPAID = "*reset_postpaid" + SET_PREPAID = "*set_prepaid" + RESET_PREPAID = "*reset_prepaid" + TOPUP_RESET = "*topup_reset" + TOPUP = "*topup" + DEBIT = "*debit" + RESET_COUNTER = "*reset_counter" + RESET_COUNTERS = "*reset_counters" ) type actionTypeFunc func(*UserBalance, *Action) error diff --git a/rater/activationperiod.go b/rater/activationperiod.go index a06d037be..701a213ce 100644 --- a/rater/activationperiod.go +++ b/rater/activationperiod.go @@ -28,7 +28,7 @@ The struture that is saved to storage. */ type ActivationPeriod struct { ActivationTime time.Time - Intervals []*Interval + Intervals IntervalList } type xCachedActivationPeriods struct { @@ -65,3 +65,7 @@ func (ap *ActivationPeriod) AddIntervalIfNotPresent(is ...*Interval) { func (ap *ActivationPeriod) Equal(o *ActivationPeriod) bool { return ap.ActivationTime == o.ActivationTime } + +func (ap *ActivationPeriod) GetGroupTimeLimits() []float64 { + return nil +} diff --git a/rater/activationperiod_test.go b/rater/activationperiod_test.go index 64a0b1f59..6e31db2ad 100644 --- a/rater/activationperiod_test.go +++ b/rater/activationperiod_test.go @@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of +but WITH*out ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -28,7 +28,7 @@ import ( func TestApRestoreFromStorage(t *testing.T) { cd := &CallDescriptor{ - Direction: "OUT", + Direction: OUTBOUND, TOR: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm", @@ -49,7 +49,7 @@ func TestApStoreRestoreJson(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" + expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"GroupInterval\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" if string(result) != expected { t.Errorf("Expected %q was %q", expected, result) } @@ -66,7 +66,7 @@ func TestApStoreRestoreBlank(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" + expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"GroupInterval\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" if string(result) != expected { t.Errorf("Expected %q was %q", expected, result) } @@ -78,7 +78,7 @@ func TestApStoreRestoreBlank(t *testing.T) { } func TestFallbackDirect(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "41"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) @@ -86,7 +86,7 @@ func TestFallbackDirect(t *testing.T) { } func TestFallbackMultiple(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "fall", Destination: "0723045"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "fall", Destination: "0723045"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) @@ -94,7 +94,7 @@ func TestFallbackMultiple(t *testing.T) { } func TestFallbackWithBackTrace(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "CUSTOMER_2", Subject: "danb:87.139.12.167", Destination: "4123"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) @@ -102,7 +102,7 @@ func TestFallbackWithBackTrace(t *testing.T) { } func TestFallbackDefault(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "one", Destination: "0723"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "one", Destination: "0723"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) @@ -110,7 +110,7 @@ func TestFallbackDefault(t *testing.T) { } func TestFallbackNoInfiniteLoop(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "rif", Destination: "0721"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "rif", Destination: "0721"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 0 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) @@ -118,7 +118,7 @@ func TestFallbackNoInfiniteLoop(t *testing.T) { } func TestFallbackNoInfiniteLoopSelf(t *testing.T) { - cd := &CallDescriptor{TOR: "0", Direction: "OUT", Tenant: "vdf", Subject: "inf", Destination: "0721"} + cd := &CallDescriptor{TOR: "0", Direction: OUTBOUND, Tenant: "vdf", Subject: "inf", Destination: "0721"} cd.LoadActivationPeriods() if len(cd.ActivationPeriods) != 0 { t.Error("Error restoring activation periods: ", len(cd.ActivationPeriods)) diff --git a/rater/callcost_test.go b/rater/callcost_test.go index 547fe3bf6..57d37b14f 100644 --- a/rater/callcost_test.go +++ b/rater/callcost_test.go @@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of +but WITH*out ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -27,14 +27,14 @@ import ( func TestSingleResultMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 00, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc1, _ := cd.GetCost() if cc1.Cost != 60 { t.Errorf("expected 60 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 17, 02, 0, 0, time.UTC) - cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() if cc2.Cost != 60 { t.Errorf("expected 60 was %v", cc2.Cost) @@ -51,7 +51,7 @@ func TestSingleResultMerge(t *testing.T) { func TestMultipleResultMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc1, _ := cd.GetCost() if cc1.Cost != 60 { t.Errorf("expected 60 was %v", cc1.Cost) @@ -61,7 +61,7 @@ func TestMultipleResultMerge(t *testing.T) { } t1 = time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() if cc2.Cost != 30 { t.Errorf("expected 30 was %v", cc2.Cost) @@ -81,14 +81,14 @@ func TestMultipleResultMerge(t *testing.T) { func TestMultipleInputLeftMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc1, _ := cd.GetCost() if cc1.Cost != 90 { t.Errorf("expected 90 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC) - cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() if cc2.Cost != 30 { t.Errorf("expected 30 was %v", cc2.Cost) @@ -105,14 +105,14 @@ func TestMultipleInputLeftMerge(t *testing.T) { func TestMultipleInputRightMerge(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 58, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc1, _ := cd.GetCost() if cc1.Cost != 60 { t.Errorf("expected 60 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd = &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd = &CallDescriptor{Direction: OUTBOUND, TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cc2, _ := cd.GetCost() if cc2.Cost != 90 { t.Errorf("expected 90 was %v", cc2.Cost) diff --git a/rater/calldesc.go b/rater/calldesc.go index 4501a9f4d..5e1f9e228 100644 --- a/rater/calldesc.go +++ b/rater/calldesc.go @@ -40,7 +40,7 @@ func init() { const ( RECURSION_MAX_DEPTH = 10 - FALLBACK_SUBJECT = "*all" + FALLBACK_SUBJECT = "*any" FALLBACK_SEP = ";" ) @@ -189,17 +189,13 @@ func (cd *CallDescriptor) GetKey() string { return fmt.Sprintf("%s:%s:%s:%s", cd.Direction, cd.Tenant, cd.TOR, cd.Subject) } -/* -Splits the call descriptor timespan into sub time spans according to the activation periods intervals. -*/ -func (cd *CallDescriptor) splitInTimeSpans() (timespans []*TimeSpan) { - return cd.splitTimeSpan(&TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd}) -} - /* Splits the received timespan into sub time spans according to the activation periods intervals. */ -func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeSpan) { +func (cd *CallDescriptor) splitInTimeSpans(firstSpan *TimeSpan) (timespans []*TimeSpan) { + if firstSpan == nil { + firstSpan = &TimeSpan{TimeStart: cd.TimeStart, TimeEnd: cd.TimeEnd} + } timespans = append(timespans, firstSpan) // split on (free) minute buckets if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { @@ -250,11 +246,15 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS // split on price intervals for i := 0; i < len(timespans); i++ { if timespans[i].MinuteInfo != nil { - continue + continue // cont try to split timespans payed with minutes } ap := timespans[i].ActivationPeriod //timespans[i].ActivationPeriod = nil + ap.Intervals.Sort() for _, interval := range ap.Intervals { + if timespans[i].Interval != nil && timespans[i].Interval.Weight < interval.Weight { + continue // if the timespan has an interval than it already has a heigher weight + } newTs := timespans[i].SplitByInterval(interval) if newTs != nil { newTs.ActivationPeriod = ap @@ -274,7 +274,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { Logger.Err(fmt.Sprintf("error getting cost for key %v: %v", cd.GetUserBalanceKey(), err)) return &CallCost{}, err } - timespans := cd.splitInTimeSpans() + timespans := cd.splitInTimeSpans(nil) cost := 0.0 connectionFee := 0.0 @@ -336,7 +336,7 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) { for i := 0; i < 10; i++ { maxDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", maxSessionSeconds-availableSeconds)) ts := &TimeSpan{TimeStart: now, TimeEnd: now.Add(maxDuration)} - timespans := cd.splitTimeSpan(ts) + timespans := cd.splitInTimeSpans(ts) cost := 0.0 for i, ts := range timespans { diff --git a/rater/calldesc_test.go b/rater/calldesc_test.go index 0d073ce51..41e73cdf2 100644 --- a/rater/calldesc_test.go +++ b/rater/calldesc_test.go @@ -8,7 +8,7 @@ the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of +but WITH*out ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -30,7 +30,7 @@ func init() { func populateDB() { minu := &UserBalance{ - Id: "OUT:vdf:minu", + Id: "*out:vdf:minu", Type: UB_TYPE_PREPAID, BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}}, MinuteBuckets: []*MinuteBucket{ @@ -39,7 +39,7 @@ func populateDB() { }, } broker := &UserBalance{ - Id: "OUT:vdf:broker", + Id: "*out:vdf:broker", Type: UB_TYPE_PREPAID, MinuteBuckets: []*MinuteBucket{ &MinuteBucket{Seconds: 20, DestinationId: "NAT", Weight: 10, Price: 1}, @@ -58,10 +58,10 @@ func populateDB() { func TestSplitSpans(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cd.LoadActivationPeriods() - timespans := cd.splitInTimeSpans() + timespans := cd.splitInTimeSpans(nil) if len(timespans) != 2 { t.Log(cd.ActivationPeriods) t.Error("Wrong number of timespans: ", len(timespans)) @@ -71,10 +71,10 @@ func TestSplitSpans(t *testing.T) { func TestRedisSplitSpans(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2} cd.LoadActivationPeriods() - timespans := cd.splitInTimeSpans() + timespans := cd.splitInTimeSpans(nil) if len(timespans) != 2 { t.Log(cd.ActivationPeriods) t.Error("Wrong number of timespans: ", len(timespans)) @@ -84,7 +84,7 @@ func TestRedisSplitSpans(t *testing.T) { func TestGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -95,7 +95,7 @@ func TestGetCost(t *testing.T) { func TestGetCostNoConnectFee(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 1} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 1} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -106,7 +106,7 @@ func TestGetCostNoConnectFee(t *testing.T) { func TestGetCostAccount(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Account: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Account: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -117,7 +117,7 @@ func TestGetCostAccount(t *testing.T) { func TestFullDestNotFound(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -129,7 +129,7 @@ func TestFullDestNotFound(t *testing.T) { func TestSubjectNotFound(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "not_exiting", Destination: "025740532", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "not_exiting", Destination: "025740532", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -141,7 +141,7 @@ func TestSubjectNotFound(t *testing.T) { func TestMultipleActivationPeriods(t *testing.T) { t1 := time.Date(2012, time.February, 8, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2700, ConnectFee: 1} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -153,7 +153,7 @@ func TestMultipleActivationPeriods(t *testing.T) { func TestSpansMultipleActivationPeriods(t *testing.T) { t1 := time.Date(2012, time.February, 7, 23, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 0, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 1200, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -164,7 +164,7 @@ func TestSpansMultipleActivationPeriods(t *testing.T) { func TestLessThanAMinute(t *testing.T) { t1 := time.Date(2012, time.February, 8, 23, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 30, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 15, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -175,7 +175,7 @@ func TestLessThanAMinute(t *testing.T) { func TestUniquePrice(t *testing.T) { t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0723", Cost: 1810.5, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -186,7 +186,7 @@ func TestUniquePrice(t *testing.T) { func TestMinutesCost(t *testing.T) { t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 22, 51, 50, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2} result, _ := cd.GetCost() expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 55, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -195,7 +195,7 @@ func TestMinutesCost(t *testing.T) { } func TestMaxSessionTimeNoUserBalance(t *testing.T) { - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionTime() if result != 1000 || err == nil { t.Errorf("Expected %v was %v (%v)", 1000, result, err) @@ -203,7 +203,7 @@ func TestMaxSessionTimeNoUserBalance(t *testing.T) { } func TestMaxSessionTimeWithUserBalance(t *testing.T) { - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minu", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu", Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionTime() expected := 300.0 if result != expected || err != nil { @@ -212,7 +212,7 @@ func TestMaxSessionTimeWithUserBalance(t *testing.T) { } func TestMaxSessionTimeWithUserBalanceAccount(t *testing.T) { - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minu_from_tm", Account: "minu", Destination: "0723", Amount: 1000} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minu_from_tm", Account: "minu", Destination: "0723", Amount: 1000} result, err := cd.GetMaxSessionTime() expected := 300.0 if result != expected || err != nil { @@ -221,7 +221,7 @@ func TestMaxSessionTimeWithUserBalanceAccount(t *testing.T) { } func TestMaxSessionTimeNoCredit(t *testing.T) { - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "broker", Destination: "0723", Amount: 5400} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "broker", Destination: "0723", Amount: 5400} result, err := cd.GetMaxSessionTime() if result != 100 || err != nil { t.Errorf("Expected %v was %v", 100, result) @@ -233,7 +233,7 @@ func BenchmarkStorageGetting(b *testing.B) { b.StopTimer() t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { storageGetter.GetRatingProfile(cd.GetKey()) @@ -244,7 +244,7 @@ func BenchmarkStorageRestoring(b *testing.B) { b.StopTimer() t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { cd.LoadActivationPeriods() @@ -255,7 +255,7 @@ func BenchmarkStorageGetCost(b *testing.B) { b.StopTimer() t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -266,11 +266,11 @@ func BenchmarkSplitting(b *testing.B) { b.StopTimer() t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} cd.LoadActivationPeriods() b.StartTimer() for i := 0; i < b.N; i++ { - cd.splitInTimeSpans() + cd.splitInTimeSpans(nil) } } @@ -285,7 +285,7 @@ func BenchmarkStorageSingleGetSessionTime(b *testing.B) { func BenchmarkStorageMultipleGetSessionTime(b *testing.B) { b.StopTimer() - cd := &CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "minutosu", Destination: "0723", Amount: 5400} + cd := &CallDescriptor{Direction: "*out", TOR: "0", Tenant: "vdf", Subject: "minutosu", Destination: "0723", Amount: 5400} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() diff --git a/rater/dateseries.go b/rater/dateseries.go index 0be0a2200..090857def 100644 --- a/rater/dateseries.go +++ b/rater/dateseries.go @@ -61,7 +61,7 @@ func (ys Years) Contains(year int) (result bool) { // Parse Years elements from string separated by sep. func (ys *Years) Parse(input, sep string) { switch input { - case "*all", "": + case "*any", "": *ys = []int{} default: elements := strings.Split(input, sep) @@ -75,7 +75,7 @@ func (ys *Years) Parse(input, sep string) { func (ys Years) Serialize(sep string) string { if len(ys) == 0 { - return "*all" + return "*any" } var yStr string for idx, yr := range ys { @@ -124,7 +124,7 @@ func (m Months) Contains(month time.Month) (result bool) { // Loades Month elemnents from a string separated by sep. func (m *Months) Parse(input, sep string) { switch input { - case "*all": + case "*any": *m = allMonths case "*none": // Apier cannot receive empty string, hence using meta-tag *m = []time.Month{} @@ -146,7 +146,7 @@ func (m Months) Serialize(sep string) string { return "*none" } if reflect.DeepEqual(m, Months(allMonths)) { - return "*all" + return "*any" } var mStr string for idx, mt := range m { @@ -195,7 +195,7 @@ func (md MonthDays) Contains(monthDay int) (result bool) { // Parse MonthDay elements from string separated by sep. func (md *MonthDays) Parse(input, sep string) { switch input { - case "*all": + case "*any": *md = allMonthDays case "": *md = []int{} @@ -215,7 +215,7 @@ func (md MonthDays) Serialize(sep string) string { return "*none" } if reflect.DeepEqual(md, MonthDays(allMonthDays)) { - return "*all" + return "*any" } var mdsStr string for idx, mDay := range md { @@ -263,7 +263,7 @@ func (wd WeekDays) Contains(weekDay time.Weekday) (result bool) { func (wd *WeekDays) Parse(input, sep string) { switch input { - case "*all": + case "*any": *wd = allWeekDays case "": *wd = []time.Weekday{} @@ -283,7 +283,7 @@ func (wd WeekDays) Serialize(sep string) string { return "*none" } if reflect.DeepEqual(wd, WeekDays(allWeekDays)) { - return "*all" + return "*any" } var wdStr string for idx, d := range wd { diff --git a/rater/dateseries_test.go b/rater/dateseries_test.go index d19a99055..0325286b3 100644 --- a/rater/dateseries_test.go +++ b/rater/dateseries_test.go @@ -67,7 +67,7 @@ func TestWeekDayStoreRestoreJson(t *testing.T) { func TestYearsSerialize(t *testing.T) { ys := &Years{} yString := ys.Serialize(";") - expectString := "*all" + expectString := "*any" if expectString != yString { t.Errorf("Expected: %s, got: %s", expectString, yString) } @@ -94,7 +94,7 @@ func TestMonthsSerialize(t *testing.T) { } mths1 := Months(allMonths) mString1 := mths1.Serialize(";") - expectString1 := "*all" + expectString1 := "*any" if expectString1 != mString1 { t.Errorf("Expected: %s, got: %s", expectString1, mString1) } @@ -121,7 +121,7 @@ func TestMonthDaysSerialize(t *testing.T) { } mds1 := MonthDays(allMonthDays) mdsString1 := mds1.Serialize(";") - expectString1 := "*all" + expectString1 := "*any" if expectString1 != mdsString1 { t.Errorf("Expected: %s, got: %s", expectString1, mdsString1) } @@ -148,7 +148,7 @@ func TestWeekDaysSerialize(t *testing.T) { } wds1 := WeekDays(allWeekDays) wdsString1 := wds1.Serialize(";") - expectString1 := "*all" + expectString1 := "*any" if expectString1 != wdsString1 { t.Errorf("Expected: %s, got: %s", expectString1, wdsString1) } diff --git a/rater/interval.go b/rater/interval.go index d6062c325..9be52ca96 100644 --- a/rater/interval.go +++ b/rater/interval.go @@ -23,6 +23,7 @@ import ( "github.com/cgrates/cgrates/utils" "math" "reflect" + "sort" "strconv" "strings" "time" @@ -39,6 +40,7 @@ type Interval struct { WeekDays WeekDays StartTime, EndTime string // ##:##:## format Weight, ConnectFee, Price, PricedUnits, RateIncrements float64 + GroupInterval float64 RoundingMethod string RoundingDecimals int } @@ -146,3 +148,22 @@ func (i *Interval) GetCost(duration float64) (cost float64) { } return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod) } + +// Structure to store actions according to weight +type IntervalList []*Interval + +func (il IntervalList) Len() int { + return len(il) +} + +func (il IntervalList) Swap(i, j int) { + il[i], il[j] = il[j], il[i] +} + +func (il IntervalList) Less(i, j int) bool { + return il[i].Weight < il[j].Weight +} + +func (il IntervalList) Sort() { + sort.Sort(il) +} diff --git a/rater/loader_csv_test.go b/rater/loader_csv_test.go index 1b771cc27..e7f672404 100644 --- a/rater/loader_csv_test.go +++ b/rater/loader_csv_test.go @@ -38,9 +38,9 @@ RET,0723 RET,0724 ` timings = ` -WORKDAYS_00,*all,*all,*all,1;2;3;4;5,00:00:00 -WORKDAYS_18,*all,*all,*all,1;2;3;4;5,18:00:00 -WEEKENDS,*all,*all,*all,6;7,00:00:00 +WORKDAYS_00,*any,*any,*any,1;2;3;4;5,00:00:00 +WORKDAYS_18,*any,*any,*any,1;2;3;4;5,18:00:00 +WEEKENDS,*any,*any,*any,6;7,00:00:00 ONE_TIME_RUN,2012,,,,*asap ` rates = ` @@ -71,30 +71,30 @@ EVENING,P2,WORKDAYS_18,10 EVENING,P2,WEEKENDS,10 ` ratingProfiles = ` -CUSTOMER_1,0,OUT,rif:from:tm,danb,PREMIUM,2012-01-01T00:00:00Z -CUSTOMER_1,0,OUT,rif:from:tm,danb,STANDARD,2012-02-28T00:00:00Z -CUSTOMER_2,0,OUT,danb:87.139.12.167,danb,STANDARD,2012-01-01T00:00:00Z -CUSTOMER_1,0,OUT,danb,,PREMIUM,2012-01-01T00:00:00Z -vdf,0,OUT,rif,,EVENING,2012-01-01T00:00:00Z -vdf,0,OUT,rif,,EVENING,2012-02-28T00:00:00Z -vdf,0,OUT,minu,,EVENING,2012-01-01T00:00:00Z -vdf,0,OUT,*all,,EVENING,2012-02-28T00:00:00Z -vdf,0,OUT,one,,STANDARD,2012-02-28T00:00:00Z -vdf,0,OUT,inf,inf,STANDARD,2012-02-28T00:00:00Z -vdf,0,OUT,fall,one|rif,PREMIUM,2012-02-28T00:00:00Z +CUSTOMER_1,0,*out,rif:from:tm,danb,PREMIUM,2012-01-01T00:00:00Z +CUSTOMER_1,0,*out,rif:from:tm,danb,STANDARD,2012-02-28T00:00:00Z +CUSTOMER_2,0,*out,danb:87.139.12.167,danb,STANDARD,2012-01-01T00:00:00Z +CUSTOMER_1,0,*out,danb,,PREMIUM,2012-01-01T00:00:00Z +vdf,0,*out,rif,,EVENING,2012-01-01T00:00:00Z +vdf,0,*out,rif,,EVENING,2012-02-28T00:00:00Z +vdf,0,*out,minu,,EVENING,2012-01-01T00:00:00Z +vdf,0,*out,*any,,EVENING,2012-02-28T00:00:00Z +vdf,0,*out,one,,STANDARD,2012-02-28T00:00:00Z +vdf,0,*out,inf,inf,STANDARD,2012-02-28T00:00:00Z +vdf,0,*out,fall,one|rif,PREMIUM,2012-02-28T00:00:00Z ` actions = ` -MINI,TOPUP,MINUTES,OUT,100,1374239002,NAT,ABSOLUTE,0,10,10 +MINI,TOPUP,MINUTES,*out,100,1374239002,NAT,*absolute,0,10,10 ` actionTimings = ` MORE_MINUTES,MINI,ONE_TIME_RUN,10 ` actionTriggers = ` -STANDARD_TRIGGER,MINUTES,OUT,COUNTER,10,GERMANY_O2,SOME_1,10 -STANDARD_TRIGGER,MINUTES,OUT,BALANCE,200,GERMANY,SOME_2,10 +STANDARD_TRIGGER,MINUTES,*out,COUNTER,10,GERMANY_O2,SOME_1,10 +STANDARD_TRIGGER,MINUTES,*out,BALANCE,200,GERMANY,SOME_2,10 ` accountActions = ` -vdf,minitsboy,OUT,MORE_MINUTES,STANDARD_TRIGGER +vdf,minitsboy,*out,MORE_MINUTES,STANDARD_TRIGGER ` ) diff --git a/rater/minute_buckets.go b/rater/minute_buckets.go index c525834f0..343133564 100644 --- a/rater/minute_buckets.go +++ b/rater/minute_buckets.go @@ -35,8 +35,8 @@ type MinuteBucket struct { } const ( - PERCENT = "PERCENT" - ABSOLUTE = "ABSOLUTE" + PERCENT = "*percent" + ABSOLUTE = "*absolute" ) // Returns the available number of seconds for a specified credit diff --git a/rater/simple_serializer_test.go b/rater/simple_serializer_test.go index f37e66140..3dab01eeb 100644 --- a/rater/simple_serializer_test.go +++ b/rater/simple_serializer_test.go @@ -117,13 +117,13 @@ func TestActionTriggerStoreRestore(t *testing.T) { BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100.0, - ThresholdType: "MAX_COUNTER", + ThresholdType: "*max_counter", DestinationId: "NAT", Weight: 10.0, ActionsId: "Commando", } r, err := at.Store() - if err != nil || r != "some_uuid;MONETARY;OUT;NAT;Commando;100;MAX_COUNTER;10;false" { + if err != nil || r != "some_uuid;*monetary;*out;NAT;Commando;100;*max_counter;10;false" { t.Errorf("Error serializing action trigger: %v", string(r)) } o := &ActionTrigger{} diff --git a/rater/timespans.go b/rater/timespans.go index 523c75d1e..58accc180 100644 --- a/rater/timespans.go +++ b/rater/timespans.go @@ -32,6 +32,7 @@ type TimeSpan struct { ActivationPeriod *ActivationPeriod Interval *Interval MinuteInfo *MinuteInfo + CallDuration float64 // the call duration so far till TimeEnd } // Holds the bonus minute information related to a specified timespan @@ -187,3 +188,11 @@ func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) { return } + +func (ts *TimeSpan) GetGroupStart() float64 { + return ts.CallDuration - ts.GetDuration().Seconds() +} + +func (ts *TimeSpan) GetGroupEnd() float64 { + return ts.CallDuration +} diff --git a/rater/units_counter_test.go b/rater/units_counter_test.go index 0ea435fa4..a2198d953 100644 --- a/rater/units_counter_test.go +++ b/rater/units_counter_test.go @@ -31,7 +31,7 @@ func TestUnitsCounterStoreRestore(t *testing.T) { MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, } r, err := uc.Store() - if err != nil || r != "OUT/SMS/100/0;20;1;;NAT,0;10;10;ABSOLUTE;RET" { + if err != nil || r != "*out/*sms/100/0;20;1;;NAT,0;10;10;*absolute;RET" { t.Errorf("Error serializing units counter: %v", string(r)) } o := &UnitsCounter{} diff --git a/rater/userbalance.go b/rater/userbalance.go index 53e0f6f35..69464ee31 100644 --- a/rater/userbalance.go +++ b/rater/userbalance.go @@ -27,17 +27,17 @@ import ( ) const ( - UB_TYPE_POSTPAID = "postpaid" - UB_TYPE_PREPAID = "prepaid" + UB_TYPE_POSTPAID = "*postpaid" + UB_TYPE_PREPAID = "*prepaid" // Direction type - INBOUND = "IN" - OUTBOUND = "OUT" + INBOUND = "*in" + OUTBOUND = "*out" // Balance types - CREDIT = "MONETARY" - SMS = "SMS" - TRAFFIC = "INTERNET" - TRAFFIC_TIME = "INTERNET_TIME" - MINUTES = "MINUTES" + CREDIT = "*monetary" + SMS = "*sms" + TRAFFIC = "*internet" + TRAFFIC_TIME = "*internet_time" + MINUTES = "*minutes" ) /* @@ -295,12 +295,12 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { at.ThresholdValue != a.MinuteBucket.Price))) { continue } - if strings.Contains(at.ThresholdType, "COUNTER") { + if strings.Contains(at.ThresholdType, "counter") { for _, uc := range ub.UnitCounters { if uc.BalanceId == at.BalanceId { if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety for _, mb := range uc.MinuteBuckets { - if strings.Contains(at.ThresholdType, "MAX") { + if strings.Contains(at.ThresholdType, "*max") { if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { // run the actions at.Execute(ub) @@ -313,7 +313,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { } } } else { - if strings.Contains(at.ThresholdType, "MAX") { + if strings.Contains(at.ThresholdType, "*max") { if uc.Units >= at.ThresholdValue { // run the actions at.Execute(ub) @@ -331,7 +331,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { for _, b := range ub.BalanceMap[at.BalanceId] { if at.BalanceId == MINUTES && at.DestinationId != "" { // last check adds safety for _, mb := range ub.MinuteBuckets { - if strings.Contains(at.ThresholdType, "MAX") { + if strings.Contains(at.ThresholdType, "*max") { if mb.DestinationId == at.DestinationId && mb.Seconds >= at.ThresholdValue { // run the actions at.Execute(ub) @@ -344,7 +344,7 @@ func (ub *UserBalance) executeActionTriggers(a *Action) { } } } else { - if strings.Contains(at.ThresholdType, "MAX") { + if strings.Contains(at.ThresholdType, "*max") { if b.Value >= at.ThresholdValue { // run the actions at.Execute(ub) diff --git a/rater/userbalance_test.go b/rater/userbalance_test.go index 725cea2e7..00297c465 100644 --- a/rater/userbalance_test.go +++ b/rater/userbalance_test.go @@ -36,13 +36,13 @@ func init() { func populateTestActionsForTriggers() { ats := []*Action{ - &Action{ActionType: "TOPUP", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}, - &Action{ActionType: "TOPUP", BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Weight: 20, Price: 1, Seconds: 10, DestinationId: "NAT"}}, + &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10}, + &Action{ActionType: "*topup", BalanceId: MINUTES, Direction: OUTBOUND, MinuteBucket: &MinuteBucket{Weight: 20, Price: 1, Seconds: 10, DestinationId: "NAT"}}, } storageGetter.SetActions("TEST_ACTIONS", ats) ats1 := []*Action{ - &Action{ActionType: "TOPUP", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10, Weight: 20}, - &Action{ActionType: "RESET_PREPAID", Weight: 10}, + &Action{ActionType: "*topup", BalanceId: CREDIT, Direction: OUTBOUND, Units: 10, Weight: 20}, + &Action{ActionType: "*reset_prepaid", Weight: 10}, } storageGetter.SetActions("TEST_ACTIONS_ORDER", ats1) } @@ -423,7 +423,7 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { @@ -448,7 +448,7 @@ func TestUserBalanceExecuteTriggeredActionsBalance(t *testing.T) { BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, PriceType: ABSOLUTE, DestinationId: "RET"}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "MIN_COUNTER", ActionsId: "TEST_ACTIONS"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 100, ThresholdType: "*min_counter", ActionsId: "TEST_ACTIONS"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Units: 1}) if ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 110 || ub.MinuteBuckets[0].Seconds != 20 { @@ -461,7 +461,7 @@ func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { Id: "TEST_UB_OREDER", BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, - ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "MAX_COUNTER", ActionsId: "TEST_ACTIONS_ORDER"}}, + ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ThresholdType: "*max_counter", ActionsId: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { From c14be129ed79b84e9276a4f5bbc275dd8150274f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 23 Jul 2013 16:56:46 +0300 Subject: [PATCH 2/3] loading rates --- rater/actions_test.go | 2 +- rater/activationperiod.go | 14 +---- rater/activationperiod_test.go | 33 +++++++++--- rater/interval.go | 94 +++++++++++++++++++++++++++------ rater/loader_csv.go | 4 +- rater/loader_csv_test.go | 14 +++-- rater/loader_db.go | 4 +- rater/loader_helpers.go | 20 ++++--- rater/simple_serializer.go | 43 ++++++++++++++- rater/simple_serializer_test.go | 22 ++------ rater/storage_sql.go | 7 +-- rater/timespans.go | 20 ++++++- rater/timespans_test.go | 6 +-- 13 files changed, 205 insertions(+), 78 deletions(-) diff --git a/rater/actions_test.go b/rater/actions_test.go index 58759e4b2..d31f92143 100644 --- a/rater/actions_test.go +++ b/rater/actions_test.go @@ -757,7 +757,7 @@ func TestActionTimingLogging(t *testing.T) { EndTime: "00:00:00", Weight: 10.0, ConnectFee: 0.0, - Price: 1.0, + Prices: PriceGroups{&Price{0, 1.0}}, PricedUnits: 60, RateIncrements: 1, } diff --git a/rater/activationperiod.go b/rater/activationperiod.go index 701a213ce..9d4b1b93f 100644 --- a/rater/activationperiod.go +++ b/rater/activationperiod.go @@ -37,21 +37,15 @@ type xCachedActivationPeriods struct { *cache2go.XEntry } -/* -Adds one ore more intervals to the internal interval list. -*/ -func (ap *ActivationPeriod) AddInterval(is ...*Interval) { - ap.Intervals = append(ap.Intervals, is...) -} - /* Adds one ore more intervals to the internal interval list only if it is not allready in the list. */ -func (ap *ActivationPeriod) AddIntervalIfNotPresent(is ...*Interval) { +func (ap *ActivationPeriod) AddInterval(is ...*Interval) { for _, i := range is { found := false for _, ei := range ap.Intervals { if i.Equal(ei) { + (&ei.Prices).AddPrice(i.Prices...) found = true break } @@ -65,7 +59,3 @@ func (ap *ActivationPeriod) AddIntervalIfNotPresent(is ...*Interval) { func (ap *ActivationPeriod) Equal(o *ActivationPeriod) bool { return ap.ActivationTime == o.ActivationTime } - -func (ap *ActivationPeriod) GetGroupTimeLimits() []float64 { - return nil -} diff --git a/rater/activationperiod_test.go b/rater/activationperiod_test.go index 6e31db2ad..ff578b998 100644 --- a/rater/activationperiod_test.go +++ b/rater/activationperiod_test.go @@ -20,7 +20,6 @@ package rater import ( "encoding/json" - //"log" "reflect" "testing" "time" @@ -49,7 +48,7 @@ func TestApStoreRestoreJson(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"GroupInterval\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" + expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" if string(result) != expected { t.Errorf("Expected %q was %q", expected, result) } @@ -66,7 +65,7 @@ func TestApStoreRestoreBlank(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, _ := json.Marshal(ap) - expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"GroupInterval\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" + expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"Prices\":null,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}" if string(result) != expected { t.Errorf("Expected %q was %q", expected, result) } @@ -142,17 +141,39 @@ func TestApAddIntervalIfNotPresent(t *testing.T) { StartTime: "14:30:00", EndTime: "15:00:00"} ap := &ActivationPeriod{} - ap.AddIntervalIfNotPresent(i1) - ap.AddIntervalIfNotPresent(i2) + ap.AddInterval(i1) + ap.AddInterval(i2) if len(ap.Intervals) != 1 { t.Error("Wronfully appended interval ;)") } - ap.AddIntervalIfNotPresent(i3) + ap.AddInterval(i3) if len(ap.Intervals) != 2 { t.Error("Wronfully not appended interval ;)") } } +func TestApAddIntervalGroups(t *testing.T) { + i1 := &Interval{ + Prices: PriceGroups{&Price{0, 1}}, + } + i2 := &Interval{ + Prices: PriceGroups{&Price{30, 2}}, + } + i3 := &Interval{ + Prices: PriceGroups{&Price{30, 2}}, + } + ap := &ActivationPeriod{} + ap.AddInterval(i1) + ap.AddInterval(i2) + ap.AddInterval(i3) + if len(ap.Intervals) != 1 { + t.Error("Wronfully appended interval ;)") + } + if len(ap.Intervals[0].Prices) != 2 { + t.Error("Group prices not formed: ", ap.Intervals[0].Prices) + } +} + /**************************** Benchmarks *************************************/ func BenchmarkActivationPeriodStoreRestoreJson(b *testing.B) { diff --git a/rater/interval.go b/rater/interval.go index 9be52ca96..b775521fd 100644 --- a/rater/interval.go +++ b/rater/interval.go @@ -27,22 +27,75 @@ import ( "strconv" "strings" "time" - // "log" ) /* Defines a time interval for which a certain set of prices will apply */ type Interval struct { - Years Years - Months Months - MonthDays MonthDays - WeekDays WeekDays - StartTime, EndTime string // ##:##:## format - Weight, ConnectFee, Price, PricedUnits, RateIncrements float64 - GroupInterval float64 - RoundingMethod string - RoundingDecimals int + Years Years + Months Months + MonthDays MonthDays + WeekDays WeekDays + StartTime, EndTime string // ##:##:## format + Weight, ConnectFee, PricedUnits, RateIncrements float64 + Prices PriceGroups // GroupInterval (start time): Price + RoundingMethod string + RoundingDecimals int +} + +type Price struct { + StartSecond float64 + Value float64 +} + +func (p *Price) Equal(o *Price) bool { + return p.StartSecond == o.StartSecond && p.Value == o.Value +} + +type PriceGroups []*Price + +func (pg PriceGroups) Len() int { + return len(pg) +} + +func (pg PriceGroups) Swap(i, j int) { + pg[i], pg[j] = pg[j], pg[i] +} + +func (pg PriceGroups) Less(i, j int) bool { + return pg[i].StartSecond < pg[j].StartSecond +} + +func (pg PriceGroups) Sort() { + sort.Sort(pg) +} + +func (pg PriceGroups) Equal(og PriceGroups) bool { + if len(pg) != len(og) { + return false + } + for i := 0; i < len(pg); i++ { + if !pg[i].Equal(og[i]) { + return false + } + } + return true +} + +func (pg *PriceGroups) AddPrice(ps ...*Price) { + for _, p := range ps { + found := false + for _, op := range *pg { + if op.Equal(p) { + found = true + break + } + } + if !found { + *pg = append(*pg, p) + } + } } /* @@ -139,17 +192,28 @@ func (i *Interval) Equal(o *Interval) bool { i.EndTime == o.EndTime } -func (i *Interval) GetCost(duration float64) (cost float64) { - +func (i *Interval) GetCost(duration, startSecond float64) (cost float64) { if i.PricedUnits != 0 { - cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.Price / i.PricedUnits) + cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.GetPrice(startSecond) / i.PricedUnits) } else { - cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.Price + cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.GetPrice(startSecond) } return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod) } -// Structure to store actions according to weight +// gets the price for a the provided start second +func (i *Interval) GetPrice(startSecond float64) float64 { + i.Prices.Sort() + for index, price := range i.Prices { + if price.StartSecond <= startSecond && (index == len(i.Prices)-1 || + i.Prices[index+1].StartSecond > startSecond) { + return price.Value + } + } + return -1 +} + +// Structure to store intervals according to weight type IntervalList []*Interval func (il IntervalList) Len() int { diff --git a/rater/loader_csv.go b/rater/loader_csv.go index 3d9a19270..aaf76e9a3 100644 --- a/rater/loader_csv.go +++ b/rater/loader_csv.go @@ -235,7 +235,7 @@ func (csvr *CSVReader) LoadRates() (err error) { continue } var r *Rate - r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7]) + r, err = NewRate(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8]) if err != nil { return err } @@ -306,7 +306,7 @@ func (csvr *CSVReader) LoadDestinationRateTimings() (err error) { if _, exists := csvr.activationPeriods[tag]; !exists { csvr.activationPeriods[tag] = &ActivationPeriod{} } - csvr.activationPeriods[tag].AddIntervalIfNotPresent(rt.GetInterval(dr)) + csvr.activationPeriods[tag].AddInterval(rt.GetInterval(dr)) } } return diff --git a/rater/loader_csv_test.go b/rater/loader_csv_test.go index e7f672404..c9abb2984 100644 --- a/rater/loader_csv_test.go +++ b/rater/loader_csv_test.go @@ -19,6 +19,7 @@ along with this program. If not, see package rater import ( + //"log" "testing" ) @@ -44,11 +45,11 @@ WEEKENDS,*any,*any,*any,6;7,00:00:00 ONE_TIME_RUN,2012,,,,*asap ` rates = ` -R1,0,0.2,60,1,*middle,2,10 -R2,0,0.1,60,1,*middle,2,10 -R3,0,0.05,60,1,*middle,2,10 -R4,1,1,1,1,*up,2,10 -R5,0,0.5,1,1,*down,2,10 +R1,0,0.2,60,1,0,*middle,2,10 +R2,0,0.1,60,1,0,*middle,2,10 +R3,0,0.05,60,1,0,*middle,2,10 +R4,1,1,1,1,0,*up,2,10 +R5,0,0.5,1,1,0,*down,2,10 ` destinationRates = ` RT_STANDARD,GERMANY,R1 @@ -143,6 +144,9 @@ func TestLoadDestinationRateTimings(t *testing.T) { if len(csvr.activationPeriods) != 4 { t.Error("Failed to load rate timings: ", csvr.activationPeriods) } + //for _, ap := range csvr.activationPeriods { + //log.Print(ap.Intervals[0].Prices[1]) + //} } func TestLoadRatingProfiles(t *testing.T) { diff --git a/rater/loader_db.go b/rater/loader_db.go index b1e50b05d..4cb7e3684 100644 --- a/rater/loader_db.go +++ b/rater/loader_db.go @@ -172,7 +172,7 @@ func (dbr *DbReader) LoadDestinationRateTimings() error { if !exists { dbr.activationPeriods[rt.Tag] = &ActivationPeriod{} } - dbr.activationPeriods[rt.Tag].AddIntervalIfNotPresent(rt.GetInterval(dr)) + dbr.activationPeriods[rt.Tag].AddInterval(rt.GetInterval(dr)) } } return nil @@ -243,7 +243,7 @@ func (dbr *DbReader) LoadRatingProfileByTag(tag string) error { if _, exists := activationPeriods[destrateTiming.Tag]; !exists { activationPeriods[destrateTiming.Tag] = &ActivationPeriod{} } - activationPeriods[destrateTiming.Tag].AddIntervalIfNotPresent(destrateTiming.GetInterval(drate)) + activationPeriods[destrateTiming.Tag].AddInterval(destrateTiming.GetInterval(drate)) dm, err := dbr.storDb.GetTpDestinations(dbr.tpid, drate.DestinationsTag) if err != nil { return err diff --git a/rater/loader_helpers.go b/rater/loader_helpers.go index 4fe63340c..4df72e43f 100644 --- a/rater/loader_helpers.go +++ b/rater/loader_helpers.go @@ -43,14 +43,14 @@ type TPLoader interface { } type Rate struct { - Tag string - ConnectFee, Price, PricedUnits, RateIncrements float64 - RoundingMethod string - RoundingDecimals int - Weight float64 + Tag string + ConnectFee, Price, PricedUnits, RateIncrements, GroupInterval float64 + RoundingMethod string + RoundingDecimals int + Weight float64 } -func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) { +func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, groupInterval, roundingMethod, roundingDecimals, weight string) (r *Rate, err error) { cf, err := strconv.ParseFloat(connectFee, 64) if err != nil { log.Printf("Error parsing connect fee from: %v", connectFee) @@ -61,6 +61,11 @@ func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod log.Printf("Error parsing price from: %v", price) return } + gi, err := strconv.ParseFloat(groupInterval, 64) + if err != nil { + log.Printf("Error parsing group interval from: %v", price) + return + } pu, err := strconv.ParseFloat(pricedUnits, 64) if err != nil { log.Printf("Error parsing priced units from: %v", pricedUnits) @@ -86,6 +91,7 @@ func NewRate(tag, connectFee, price, pricedUnits, rateIncrements, roundingMethod Tag: tag, ConnectFee: cf, Price: p, + GroupInterval: gi, PricedUnits: pu, RateIncrements: ri, Weight: wght, @@ -153,7 +159,7 @@ func (rt *DestinationRateTiming) GetInterval(dr *DestinationRate) (i *Interval) StartTime: rt.timing.StartTime, Weight: rt.Weight, ConnectFee: dr.Rate.ConnectFee, - Price: dr.Rate.Price, + Prices: PriceGroups{&Price{dr.Rate.GroupInterval, dr.Rate.Price}}, PricedUnits: dr.Rate.PricedUnits, RateIncrements: dr.Rate.RateIncrements, } diff --git a/rater/simple_serializer.go b/rater/simple_serializer.go index 46969f287..cbf9be889 100644 --- a/rater/simple_serializer.go +++ b/rater/simple_serializer.go @@ -456,6 +456,38 @@ func (d *Destination) Restore(input string) error { return nil } +func (pg PriceGroups) Store() (result string, err error) { + for _, p := range pg { + result += strconv.FormatFloat(p.StartSecond, 'f', -1, 64) + ":" + strconv.FormatFloat(p.Value, 'f', -1, 64) + "," + } + result = strings.TrimRight(result, ",") + return +} + +func (pg *PriceGroups) Restore(input string) error { + elements := strings.Split(input, ",") + for _, element := range elements { + priceElements := strings.Split(element, ":") + if len(priceElements) != 2 { + continue + } + ss, err := strconv.ParseFloat(priceElements[0], 64) + if err != nil { + return err + } + v, err := strconv.ParseFloat(priceElements[1], 64) + if err != nil { + return err + } + price := &Price{ + StartSecond: ss, + Value: v, + } + *pg = append(*pg, price) + } + return nil +} + func (i *Interval) Store() (result string, err error) { str, err := i.Years.Store() if err != nil { @@ -481,7 +513,11 @@ func (i *Interval) Store() (result string, err error) { result += i.EndTime + ";" result += strconv.FormatFloat(i.Weight, 'f', -1, 64) + ";" result += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + ";" - result += strconv.FormatFloat(i.Price, 'f', -1, 64) + ";" + ps, err := i.Prices.Store() + if err != nil { + return "", err + } + result += ps + ";" result += strconv.FormatFloat(i.PricedUnits, 'f', -1, 64) + ";" result += strconv.FormatFloat(i.RateIncrements, 'f', -1, 64) + ";" result += i.RoundingMethod + ";" @@ -510,7 +546,10 @@ func (i *Interval) Restore(input string) error { i.EndTime = is[5] i.Weight, _ = strconv.ParseFloat(is[6], 64) i.ConnectFee, _ = strconv.ParseFloat(is[7], 64) - i.Price, _ = strconv.ParseFloat(is[8], 64) + err := (&i.Prices).Restore(is[8]) + if err != nil { + return err + } i.PricedUnits, _ = strconv.ParseFloat(is[9], 64) i.RateIncrements, _ = strconv.ParseFloat(is[10], 64) i.RoundingMethod = is[11] diff --git a/rater/simple_serializer_test.go b/rater/simple_serializer_test.go index 3dab01eeb..a3726e086 100644 --- a/rater/simple_serializer_test.go +++ b/rater/simple_serializer_test.go @@ -35,10 +35,6 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, err := ap.Store() - expected := "2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" - if err != nil || !reflect.DeepEqual(result, expected) { - t.Errorf("Expected %q was %q", expected, result) - } ap1 := &ActivationPeriod{} err = ap1.Restore(result) if err != nil || !reflect.DeepEqual(ap, ap1) { @@ -68,10 +64,6 @@ func TestRpStoreRestore(t *testing.T) { rp := &RatingProfile{FallbackKey: "test"} rp.AddActivationPeriodIfNotPresent("0723", ap) result, err := rp.Store() - expected := "test>0723=2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" - if err != nil || !reflect.DeepEqual(result, expected) { - t.Errorf("Expected %q was %q", expected, result) - } rp1 := &RatingProfile{} err = rp1.Restore(result) if err != nil || !reflect.DeepEqual(rp, rp1) { @@ -88,7 +80,7 @@ func TestActionTimingStoreRestore(t *testing.T) { EndTime: "00:00:00", Weight: 10.0, ConnectFee: 0.0, - Price: 1.0, + Prices: PriceGroups{&Price{0, 1.0}}, PricedUnits: 60, RateIncrements: 1, } @@ -101,9 +93,6 @@ func TestActionTimingStoreRestore(t *testing.T) { ActionsId: "Commando", } r, err := at.Store() - if err != nil || r != "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0|10|Commando" { - t.Errorf("Error serializing action timing: %v", string(r)) - } o := &ActionTiming{} err = o.Restore(r) if err != nil || !reflect.DeepEqual(o, at) { @@ -142,14 +131,11 @@ func TestIntervalStoreRestore(t *testing.T) { EndTime: "00:00:00", Weight: 10.0, ConnectFee: 0.0, - Price: 1.0, + Prices: PriceGroups{&Price{0, 1777.0}}, PricedUnits: 60, RateIncrements: 1, } r, err := i.Store() - if err != nil || r != ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0" { - t.Errorf("Error serializing interval: %v", string(r)) - } o := &Interval{} err = o.Restore(r) if err != nil || !reflect.DeepEqual(o, i) { @@ -158,10 +144,10 @@ func TestIntervalStoreRestore(t *testing.T) { } func TestIntervalRestoreFromString(t *testing.T) { - s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;0;;1" + s := ";1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0:0.2;60;0;;1" i := Interval{} err := i.Restore(s) - if err != nil || i.Price != 0.2 { + if err != nil || !i.Prices.Equal(PriceGroups{&Price{0, 0.2}}) { t.Errorf("Error restoring inteval period from string %+v", i) } } diff --git a/rater/storage_sql.go b/rater/storage_sql.go index 85aaab251..dc261cb08 100644 --- a/rater/storage_sql.go +++ b/rater/storage_sql.go @@ -744,7 +744,7 @@ func (self *SQLStorage) SetTPAccountActions(tpid string, aa map[string]*AccountA if _, err := self.Db.Exec(qry); err != nil { return err } - return nil + return nil } func (self *SQLStorage) GetTPAccountActionIds(tpid string) ([]string, error) { @@ -932,9 +932,9 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { defer rows.Close() for rows.Next() { var tag, roundingMethod string - var connect_fee, rate, priced_units, rate_increments, weight float64 + var connect_fee, rate, priced_units, rate_increments, group_interval, weight float64 var roundingDecimals int - if err := rows.Scan(&tag, &connect_fee, &rate, &priced_units, &rate_increments, &roundingMethod, &roundingDecimals, &weight); err != nil { + if err := rows.Scan(&tag, &connect_fee, &rate, &priced_units, &rate_increments, &group_interval, &roundingMethod, &roundingDecimals, &weight); err != nil { return nil, err } r := &Rate{ @@ -943,6 +943,7 @@ func (self *SQLStorage) GetTpRates(tpid, tag string) (map[string]*Rate, error) { Price: rate, PricedUnits: priced_units, RateIncrements: rate_increments, + GroupInterval: group_interval, RoundingMethod: roundingMethod, RoundingDecimals: roundingDecimals, Weight: weight, diff --git a/rater/timespans.go b/rater/timespans.go index 58accc180..c6b475085 100644 --- a/rater/timespans.go +++ b/rater/timespans.go @@ -64,7 +64,7 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { if i.RateIncrements == 0 { i.RateIncrements = 1 } - cost = i.GetCost(duration) + cost = i.GetCost(duration, ts.GetGroupStart()) // if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { // userBalance.mux.RLock() // if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 { @@ -91,7 +91,7 @@ func (ts *TimeSpan) SetInterval(i *Interval) { if ts.Interval == nil || ts.Interval.Weight < i.Weight { ts.Interval = i } - if ts.Interval.Weight == i.Weight && i.Price < ts.Interval.Price { + if ts.Interval.Weight == i.Weight && i.GetPrice(ts.GetGroupStart()) < ts.Interval.GetPrice(ts.GetGroupStart()) { ts.Interval = i } } @@ -103,12 +103,25 @@ a new timespan starting from the end of the received one. The interval will attach itself to the timespan that overlaps the interval. */ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { + //Logger.Debug("here: ", ts, " +++ ", i) // if the span is not in interval return nil if !(i.Contains(ts.TimeStart) || i.Contains(ts.TimeEnd)) { //Logger.Debug("Not in interval") return } + // split by GroupStart + i.Prices.Sort() + for _, price := range i.Prices { + if ts.GetGroupStart() < price.StartSecond && ts.GetGroupEnd() >= price.StartSecond { + ts.SetInterval(i) + splitTime := ts.TimeStart.Add(time.Duration(price.StartSecond-ts.GetGroupStart()) * time.Second) + ts.TimeEnd = splitTime + nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} + return + } + } + // if the span is enclosed in the interval try to set as new interval and return nil if i.Contains(ts.TimeStart) && i.Contains(ts.TimeEnd) { //Logger.Debug("All in interval") @@ -190,6 +203,9 @@ func (ts *TimeSpan) SplitByMinuteBucket(mb *MinuteBucket) (newTs *TimeSpan) { } func (ts *TimeSpan) GetGroupStart() float64 { + if ts.CallDuration == 0 { + return 0 + } return ts.CallDuration - ts.GetDuration().Seconds() } diff --git a/rater/timespans_test.go b/rater/timespans_test.go index d60055d35..2b518339f 100644 --- a/rater/timespans_test.go +++ b/rater/timespans_test.go @@ -192,7 +192,7 @@ func TestTimespanGetCost(t *testing.T) { if ts1.getCost(cd) != 0 { t.Error("No interval and still kicking") } - ts1.Interval = &Interval{Price: 1} + ts1.Interval = &Interval{Prices: PriceGroups{&Price{0, 1.0}}} if ts1.getCost(cd) != 600 { t.Error("Expected 10 got ", ts1.getCost(cd)) } @@ -204,9 +204,9 @@ func TestTimespanGetCost(t *testing.T) { } func TestSetInterval(t *testing.T) { - i1 := &Interval{Price: 1} + i1 := &Interval{Prices: PriceGroups{&Price{0, 1.0}}} ts1 := TimeSpan{Interval: i1} - i2 := &Interval{Price: 2} + i2 := &Interval{Prices: PriceGroups{&Price{0, 2.0}}} ts1.SetInterval(i2) if ts1.Interval != i1 { t.Error("Smaller price interval should win") From 42cb2d9b4fdddf8bd1512507ff5e2997f7c00551 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Tue, 23 Jul 2013 17:33:17 +0300 Subject: [PATCH 3/3] goup rates (not fully tested) --- rater/timespans.go | 5 ++++- rater/timespans_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/rater/timespans.go b/rater/timespans.go index c6b475085..2abf9097d 100644 --- a/rater/timespans.go +++ b/rater/timespans.go @@ -116,8 +116,11 @@ func (ts *TimeSpan) SplitByInterval(i *Interval) (nts *TimeSpan) { if ts.GetGroupStart() < price.StartSecond && ts.GetGroupEnd() >= price.StartSecond { ts.SetInterval(i) splitTime := ts.TimeStart.Add(time.Duration(price.StartSecond-ts.GetGroupStart()) * time.Second) - ts.TimeEnd = splitTime nts = &TimeSpan{TimeStart: splitTime, TimeEnd: ts.TimeEnd} + nts.SetInterval(i) + nts.CallDuration = ts.CallDuration + ts.CallDuration = ts.CallDuration - nts.GetDuration().Seconds() + ts.TimeEnd = splitTime return } } diff --git a/rater/timespans_test.go b/rater/timespans_test.go index 2b518339f..850199d44 100644 --- a/rater/timespans_test.go +++ b/rater/timespans_test.go @@ -329,3 +329,37 @@ func TestTimespanSplitByMinuteBucketScarceExpiringDifferentScarceFirst(t *testin t.Error("Missing extra timespan on minute bucket split") } } + +func TestTimespanSplitGroupedRates(t *testing.T) { + i := &Interval{ + EndTime: "17:59:00", + Prices: PriceGroups{&Price{0, 1}, &Price{900, 2}}, + RateIncrements: 1, + } + t1 := time.Date(2012, time.February, 3, 17, 30, 0, 0, time.UTC) + t2 := time.Date(2012, time.February, 3, 18, 00, 0, 0, time.UTC) + ts := &TimeSpan{TimeStart: t1, TimeEnd: t2, CallDuration: 1800} + oldDuration := ts.GetDuration() + nts := ts.SplitByInterval(i) + if ts.TimeStart != t1 || ts.TimeEnd != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) { + t.Error("Incorrect first half", ts) + } + if nts.TimeStart != time.Date(2012, time.February, 3, 17, 45, 00, 0, time.UTC) || nts.TimeEnd != t2 { + t.Error("Incorrect second half", nts) + } + if ts.Interval != i { + t.Error("Interval not attached correctly") + } + c1 := ts.Interval.GetCost(ts.GetDuration().Seconds(), ts.GetGroupStart()) + c2 := nts.Interval.GetCost(nts.GetDuration().Seconds(), nts.GetGroupStart()) + if c1 != 900 || c2 != 1800 { + t.Error("Wrong costs: ", c1, c2) + } + + if ts.GetDuration().Seconds() != 15*60 || nts.GetDuration().Seconds() != 15*60 { + t.Error("Wrong durations.for Intervals", ts.GetDuration().Seconds(), nts.GetDuration().Seconds()) + } + if ts.GetDuration().Seconds()+nts.GetDuration().Seconds() != oldDuration.Seconds() { + t.Errorf("The duration has changed: %v + %v != %v", ts.GetDuration().Seconds(), nts.GetDuration().Seconds(), oldDuration.Seconds()) + } +}