From 9db39c125b2fb7701b4cc15ad3886ec3bf6b9cda Mon Sep 17 00:00:00 2001 From: Gerdriaan Mulder Date: Fri, 31 Jan 2020 22:24:51 +0100 Subject: [PATCH] WIP: pricing of CDRs, it's not enough to filter per destination --- combine.go | 58 +++++++++++++++++++++++++++++++++++-------------- import_price.go | 13 ++++++++++- record.go | 1 + 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/combine.go b/combine.go index b954167..5d15725 100644 --- a/combine.go +++ b/combine.go @@ -2,6 +2,7 @@ package cdr import ( "fmt" + "math" "strings" "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_FROM_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET 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{}) for _, l := range cdrLines { @@ -121,15 +125,22 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { // Cache destination prices _, knownNonPriced := nonMatchedDestinations[l.Destination] if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced { + matched := false priceLoop: for _, p := range prices { if p.Type != PriceCall { + fmt.Printf("[debug] skipping this price: %+v\n", p) continue priceLoop } if p.Destination == l.Destination { destinationToPrice[l.Destination] = p + matched = true break priceLoop } + + } + if !matched { + fmt.Printf("[debug] non-matched price for destination %q\n", l.Destination) 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) } - // 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) for _, l := range cdrLines { if l.Kind != VoiceLine { 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 { uniqueCalls[idx] = make([]Line, 0) } @@ -162,6 +173,8 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { } fmt.Println() */ + callCost := 0.0 + callPrice := 0.0 for _, leg := range v { legPrice := Price{} 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: // This leg consists of the airtime (PBX->HS, roaming + non-roaming) 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) case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM: // We only know the destination country @@ -202,23 +217,34 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { _, 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", 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 - fmt.Printf("(%12s->%12s) leg %d, price: %+v\n", leg.CLI, leg.To, leg.Leg, legPrice) - } - ret[i] = Call{ - From: "asdf", - To: "asdkflj", - Duration: time.Duration(0), - Cost: 0, - Price: 0, - } - /* - ret[i].Legs = make([]PricedLine, len(v)) - for j, leg := range v { - ret[i].Legs[j] = leg + //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{ + From: v[0].CLI, + To: v[0].To, + Duration: callDuration, + Time: v[0].Time, + Cost: int(math.Round(callCost)), + Price: int(math.Round(callPrice)), } - */ + } else { + + } + fmt.Printf("==> %+v\n", ret[i]) + //ret[i].Legs = make([]PricedLine, len(v)) + //for j, leg := range v { + //ret[i].Legs[j] = leg + //} i++ } diff --git a/import_price.go b/import_price.go index 65a507f..ceb3415 100644 --- a/import_price.go +++ b/import_price.go @@ -23,6 +23,7 @@ type Price struct { SellEach int SellUnit int Destination string + OurRef string } const FieldsPerPricingLine = 14 @@ -94,7 +95,17 @@ func ImportPricesFile(fn string) ([]Price, error) { } 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 var buyUnit, sellUnit int diff --git a/record.go b/record.go index 0d275d6..0ae113b 100644 --- a/record.go +++ b/record.go @@ -128,6 +128,7 @@ type PricedLine struct { type Call struct { From string To string + Time time.Time Duration time.Duration Cost int Price int