Browse Source

WIP: pricing of CDRs, it's not enough to filter per destination

master
Gerdriaan Mulder 6 years ago
parent
commit
9db39c125b
  1. 52
      combine.go
  2. 13
      import_price.go
  3. 1
      record.go

52
combine.go

@ -2,6 +2,7 @@ package cdr
import ( import (
"fmt" "fmt"
"math"
"strings" "strings"
"time" "time"
) )
@ -112,6 +113,9 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET
destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET
destinationToPrice[CALL_NATIONAL_DESTINATION_MOBILE.Destination] = CALL_NATIONAL_DESTINATION_MOBILE destinationToPrice[CALL_NATIONAL_DESTINATION_MOBILE.Destination] = CALL_NATIONAL_DESTINATION_MOBILE
destinationToPrice[CALL_NATIONAL_DESTINATION_FREEPHONE.Destination] = CALL_NATIONAL_DESTINATION_FREEPHONE
destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_NOSETUP.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_NOSETUP
destinationToPrice[CALL_NATIONAL_DESTINATION_VOICEMAIL.Destination] = CALL_NATIONAL_DESTINATION_VOICEMAIL
nonMatchedDestinations := make(map[string]struct{}) nonMatchedDestinations := make(map[string]struct{})
for _, l := range cdrLines { for _, l := range cdrLines {
@ -121,15 +125,22 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
// Cache destination prices // Cache destination prices
_, knownNonPriced := nonMatchedDestinations[l.Destination] _, knownNonPriced := nonMatchedDestinations[l.Destination]
if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced { if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced {
matched := false
priceLoop: priceLoop:
for _, p := range prices { for _, p := range prices {
if p.Type != PriceCall { if p.Type != PriceCall {
fmt.Printf("[debug] skipping this price: %+v\n", p)
continue priceLoop continue priceLoop
} }
if p.Destination == l.Destination { if p.Destination == l.Destination {
destinationToPrice[l.Destination] = p destinationToPrice[l.Destination] = p
matched = true
break priceLoop break priceLoop
} }
}
if !matched {
fmt.Printf("[debug] non-matched price for destination %q\n", l.Destination)
nonMatchedDestinations[l.Destination] = struct{}{} nonMatchedDestinations[l.Destination] = struct{}{}
} }
} }
@ -139,13 +150,13 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
fmt.Printf("[warning] no price for destination %v\n", k) fmt.Printf("[warning] no price for destination %v\n", k)
} }
// a map from "%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From // a map from "%s_%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From, l.Account
uniqueCalls := make(map[string][]Line) uniqueCalls := make(map[string][]Line)
for _, l := range cdrLines { for _, l := range cdrLines {
if l.Kind != VoiceLine { if l.Kind != VoiceLine {
continue continue
} }
idx := fmt.Sprintf("%s_%s_%s", l.Time.Format(DateTimeFormat), l.CLI, l.From) idx := fmt.Sprintf("%s_%s_%s_%s", l.Time.Format(DateTimeFormat), l.CLI, l.From, l.Account)
if _, ok := uniqueCalls[idx]; !ok { if _, ok := uniqueCalls[idx]; !ok {
uniqueCalls[idx] = make([]Line, 0) uniqueCalls[idx] = make([]Line, 0)
} }
@ -162,6 +173,8 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
} }
fmt.Println() fmt.Println()
*/ */
callCost := 0.0
callPrice := 0.0
for _, leg := range v { for _, leg := range v {
legPrice := Price{} legPrice := Price{}
if leg.CLI == leg.From { if leg.CLI == leg.From {
@ -188,6 +201,8 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
case leg.From == leg.To && leg.Source == CALL_TO_HANDSET_SOURCE && leg.Destination == CALL_TO_HANDSET_DESTINATION: case leg.From == leg.To && leg.Source == CALL_TO_HANDSET_SOURCE && leg.Destination == CALL_TO_HANDSET_DESTINATION:
// This leg consists of the airtime (PBX->HS, roaming + non-roaming) // This leg consists of the airtime (PBX->HS, roaming + non-roaming)
legPrice = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET legPrice = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET
case leg.Destination == CALL_TO_VOICEMAIL && (leg.Reason == CFNA || leg.Reason == CFBS || leg.Reason == CFIM):
legPrice = CALL_NATIONAL_DESTINATION_VOICEMAIL
// source -> HS (roaming) // source -> HS (roaming)
case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM: case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM:
// We only know the destination country // We only know the destination country
@ -202,23 +217,34 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
_, mapOk := destinationToPrice[leg.Destination] _, mapOk := destinationToPrice[leg.Destination]
fmt.Printf("[warning] non-matched CDR leg (cid: %q, dt: %v, CLI: %s, %s->%s, src: %s, dest: %s (%s) %v\n", fmt.Printf("[warning] non-matched CDR leg (cid: %q, dt: %v, CLI: %s, %s->%s, src: %s, dest: %s (%s) %v\n",
leg.Id, leg.Time.Format(DateTimeFormat), leg.CLI, leg.From, leg.To, leg.Source, leg.Destination, leg.Reason, mapOk) leg.Id, leg.Time.Format(DateTimeFormat), leg.CLI, leg.From, leg.To, leg.Source, leg.Destination, leg.Reason, mapOk)
} else {
// RawCount is in seconds. Buy/SellUnit is in cents*100, per minute
callCost += float64(legPrice.BuyEach) + float64(leg.RawCount)*(float64(legPrice.BuyUnit)/60.0)
callPrice += float64(legPrice.SellEach) + float64(leg.RawCount)*(float64(legPrice.SellUnit)/60.0)
fmt.Printf("(%12s->%12s) leg %d, callCost: %.4f, callPrice: %.4f (dst: %q)\n", leg.CLI, leg.To, leg.Leg, callCost, callPrice, legPrice.OurRef)
} }
// Source -> HS (non-roaming) (i.e. incoming calls) does not exist in the CSV // Source -> HS (non-roaming) (i.e. incoming calls) does not exist in the CSV
fmt.Printf("(%12s->%12s) leg %d, price: %+v\n", leg.CLI, leg.To, leg.Leg, legPrice) //fmt.Printf("(%12s->%12s) leg %d, price: %+v\n", leg.CLI, leg.To, leg.Leg, legPrice)
} }
if len(v) == 1 {
callDuration, _ := time.ParseDuration(fmt.Sprintf("%ds", v[0].RawCount))
// CLI == From, then this leg's CLI is our "From" field, and this leg's "To" likewise
ret[i] = Call{ ret[i] = Call{
From: "asdf", From: v[0].CLI,
To: "asdkflj", To: v[0].To,
Duration: time.Duration(0), Duration: callDuration,
Cost: 0, Time: v[0].Time,
Price: 0, Cost: int(math.Round(callCost)),
Price: int(math.Round(callPrice)),
} }
/* } else {
ret[i].Legs = make([]PricedLine, len(v))
for j, leg := range v {
ret[i].Legs[j] = leg
} }
*/ fmt.Printf("==> %+v\n", ret[i])
//ret[i].Legs = make([]PricedLine, len(v))
//for j, leg := range v {
//ret[i].Legs[j] = leg
//}
i++ i++
} }

13
import_price.go

@ -23,6 +23,7 @@ type Price struct {
SellEach int SellEach int
SellUnit int SellUnit int
Destination string Destination string
OurRef string
} }
const FieldsPerPricingLine = 14 const FieldsPerPricingLine = 14
@ -94,7 +95,17 @@ func ImportPricesFile(fn string) ([]Price, error) {
} }
for _, priceType := range priceTypes { for _, priceType := range priceTypes {
destination := line[PriceImportRegion] + " - " + line[PriceImportDestination] + " - " + priceType var destination string
if line[PriceImportRegion] == "Europe" {
destination = line[PriceImportDestination] + " - " + priceType + " - "
if priceType == "Fixed" {
destination = destination + "Proper"
} else {
destination = destination + priceType
}
} else {
destination = line[PriceImportRegion] + " - " + line[PriceImportDestination] + " - " + priceType
}
priceKind := PriceCall // TODO: import Text and Data through the same CSV priceKind := PriceCall // TODO: import Text and Data through the same CSV
var buyUnit, sellUnit int var buyUnit, sellUnit int

1
record.go

@ -128,6 +128,7 @@ type PricedLine struct {
type Call struct { type Call struct {
From string From string
To string To string
Time time.Time
Duration time.Duration Duration time.Duration
Cost int Cost int
Price int Price int

Loading…
Cancel
Save