From 63ad1bb64c6eb765a2f2dc75bc2786fe2884f32c Mon Sep 17 00:00:00 2001 From: Gerdriaan Mulder Date: Sat, 29 Feb 2020 19:05:29 +0100 Subject: [PATCH] Prepare priced CDRs for export --- cmd/cdrtool/main.go | 38 +++++++++++++++++++++++ combine.go | 34 +++++++++++++++----- import_price.go | 2 +- record.go | 13 ++++++++ sortable_records.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 sortable_records.go diff --git a/cmd/cdrtool/main.go b/cmd/cdrtool/main.go index 3c3055e..c4c5eaa 100644 --- a/cmd/cdrtool/main.go +++ b/cmd/cdrtool/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "sort" "src.wolkict.net/cdr" ) @@ -15,6 +16,7 @@ var ( priceExec = flag.Bool("execute", false, "price each CDR according to the prices file") debugImport = flag.Bool("debugImport", false, "show extra debugging info for import") debug = flag.Bool("debug", false, "show extra debugging info") + export = flag.String("export", "", "export priced calls to this csv") ) func usage() { @@ -24,6 +26,7 @@ func usage() { func main() { flag.Parse() + cdr.SetDebug(*debug) if *csvFile == "" { usage() @@ -95,5 +98,40 @@ func main() { fmt.Printf("c: %+v\n", c) } } + if *export != "" { + sc := cdr.NewSortableCalls(calls) + sort.Sort(sc) + callCost := 0 + callPrice := 0 + for _, c := range sc.Calls { + fmt.Printf("%s\n", c.String()) + callCost += c.Cost + callPrice += c.Price + } + + st := cdr.NewSortableTexts(texts) + sort.Sort(st) + textCost := 0 + textPrice := 0 + for _, t := range st.Texts { + fmt.Printf("%s\n", t.String()) + textCost += t.PricedLine.Cost + textPrice += t.PricedLine.Price + } + + sd := cdr.NewSortableData(data) + sort.Sort(sd) + dataCost := 0 + dataPrice := 0 + for _, d := range sd.Data { + fmt.Printf("%s\n", d.String()) + dataCost += d.PricedLine.Cost + dataPrice += d.PricedLine.Price + } + + fmt.Printf("callCost: %.4f, callPrice: %.4f\n", float64(callCost)/10000.0, float64(callPrice)/10000.0) + fmt.Printf("textCost: %.4f, textPrice: %.4f\n", float64(textCost)/10000.0, float64(textPrice)/10000.0) + fmt.Printf("dataCost: %.4f, dataPrice: %.4f\n", float64(dataCost)/10000.0, float64(dataPrice)/10000.0) + } } } diff --git a/combine.go b/combine.go index 1b79ef4..4b7b912 100644 --- a/combine.go +++ b/combine.go @@ -7,6 +7,14 @@ import ( "time" ) +var ( + DEBUG = false +) + +func SetDebug(d bool) { + DEBUG = d +} + func CombineTextCDRs(cdrLines []Line, prices []Price) ([]Text, error) { var err []string @@ -113,9 +121,19 @@ 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_MOBILE_SU.Destination] = CALL_NATIONAL_DESTINATION_MOBILE_SU 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_PREMIUM_BIBA.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_BIBA + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_FREE_NOSETUP.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_FREE_NOSETUP + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_10ct.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_10ct + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_45ct.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_45ct + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_50ct_NOSETUP.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_50ct_NOSETUP + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_80ct_NOSETUP.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_80ct_NOSETUP + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_50ct_CALL.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_50ct_CALL + destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_80ct_CALL.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_80ct_CALL destinationToPrice[CALL_NATIONAL_DESTINATION_VOICEMAIL.Destination] = CALL_NATIONAL_DESTINATION_VOICEMAIL + destinationToPrice[CALL_INTERNAL.Destination] = CALL_INTERNAL + destinationToPrice[CALL_UK_DESTINATION_NON_GEO.Destination] = CALL_UK_DESTINATION_NON_GEO nonMatchedDestinations := make(map[string]struct{}) for _, l := range cdrLines { @@ -129,7 +147,7 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { priceLoop: for _, p := range prices { if p.Type != PriceCall { - fmt.Printf("[debug] skipping this price: %+v\n", p) + fmt.Printf("[warning] skipping this price: %+v\n", p) continue priceLoop } if p.Destination == l.Destination { @@ -140,7 +158,7 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { } if !matched { - fmt.Printf("[debug] non-matched price for destination %q (from line: %+v)\n", l.Destination, l) + fmt.Printf("[warning] non-matched price for destination %q (from line: %+v)\n", l.Destination, l) nonMatchedDestinations[l.Destination] = struct{}{} } } @@ -233,7 +251,9 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { callCost += thisLegCost callPrice += thisLegPrice - fmt.Printf("(%12s( %12s)->%12s) leg %d, callCost: %.4f, callPrice: %.4f (dst: %q)\n", leg.CLI, leg.From, leg.To, leg.Leg, callCost, callPrice, legPrice.OurRef) + if DEBUG { + fmt.Printf("(%12s( %12s)->%12s) leg %d, callCost: %.4f, callPrice: %.4f (dst: %q)\n", leg.CLI, leg.From, leg.To, leg.Leg, callCost/10000.0, callPrice/10000.0, 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) @@ -268,7 +288,7 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { ret[i].From = callFrom ret[i].To = callTo - fmt.Printf(" ==> %+v\n\n", ret[i]) + //fmt.Printf(" ==> %+v\n\n", ret[i]) //ret[i].Legs = make([]PricedLine, len(v)) //for j, leg := range v { //ret[i].Legs[j] = leg @@ -276,6 +296,6 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { i++ } - fmt.Printf("destinationPrice: %+v\n", destinationToPrice) - return nil, fmt.Errorf("not yet implemented") + //fmt.Printf("destinationPrice: %+v\n", destinationToPrice) + return ret, nil } diff --git a/import_price.go b/import_price.go index c1e0911..7399d3d 100644 --- a/import_price.go +++ b/import_price.go @@ -97,7 +97,7 @@ func ImportPricesFile(fn string) ([]Price, error) { for _, priceType := range priceTypes { var destination string - if line[PriceImportRegion] == "Europe" { + if line[PriceImportRegion] == "Europe" || line[PriceImportRegion] == "North America" || line[PriceImportRegion] == "Africa" { destination = line[PriceImportDestination] + " - " + priceType + " - " if priceType == "Fixed" { destination = destination + "Proper" diff --git a/record.go b/record.go index 0ae113b..642f560 100644 --- a/record.go +++ b/record.go @@ -1,6 +1,7 @@ package cdr import ( + "fmt" "time" ) @@ -136,14 +137,26 @@ type Call struct { Legs []PricedLine } +func (c *Call) String() string { + return fmt.Sprintf("%s (%12s->%12s) callCost: %.4f, callPrice: %.4f (dur: %v)", c.Time.Format(DateTimeFormat), c.From, c.To, float64(c.Cost)/10000.0, float64(c.Price)/10000.0, c.Duration) +} + // Text is a specific PricedLine for a text message. type Text struct { PricedLine } +func (t *Text) String() string { + return fmt.Sprintf("%s (%12s->%12s) textCost: %.4f, textPrice: %.4f", t.PricedLine.Line.Time.Format(DateTimeFormat), t.PricedLine.Line.From, t.PricedLine.Line.To, float64(t.PricedLine.Cost)/10000.0, float64(t.PricedLine.Price)/10000.0) +} + // Data is a specific PricedLine that annotates the number of bytes consumed in that session. type Data struct { PricedLine Bytes int } + +func (d *Data) String() string { + return fmt.Sprintf("%s (%v bytes) dataCost: %.4f, dataPrice: %.4f", d.PricedLine.Line.Time.Format(DateTimeFormat), d.Bytes, float64(d.PricedLine.Cost)/10000.0, float64(d.PricedLine.Price)/10000.0) +} diff --git a/sortable_records.go b/sortable_records.go new file mode 100644 index 0000000..45cefce --- /dev/null +++ b/sortable_records.go @@ -0,0 +1,76 @@ +package cdr + +type SortableCalls struct { + Calls []Call + num int // shortcut for Len() +} + +func NewSortableCalls(c []Call) *SortableCalls { + return &SortableCalls{ + Calls: append(c[:0:0], c...), + num: len(c), + } +} + +func (sc *SortableCalls) Len() int { + return sc.num +} + +func (sc *SortableCalls) Less(i, j int) bool { + // We assume i, j < len(sc.Calls) + return sc.Calls[i].Time.Before(sc.Calls[j].Time) +} + +func (sc *SortableCalls) Swap(i, j int) { + sc.Calls[j], sc.Calls[i] = sc.Calls[i], sc.Calls[j] +} + +type SortableTexts struct { + Texts []Text + num int // shortcut for Len() +} + +func NewSortableTexts(t []Text) *SortableTexts { + return &SortableTexts{ + Texts: append(t[:0:0], t...), + num: len(t), + } +} + +func (st *SortableTexts) Len() int { + return st.num +} + +func (st *SortableTexts) Less(i, j int) bool { + // We assume i, j < len(st.Texts) + return st.Texts[i].PricedLine.Line.Time.Before(st.Texts[j].PricedLine.Line.Time) +} + +func (st *SortableTexts) Swap(i, j int) { + st.Texts[j], st.Texts[i] = st.Texts[i], st.Texts[j] +} + +type SortableData struct { + Data []Data + num int // shortcut for Len() +} + +func NewSortableData(d []Data) *SortableData { + return &SortableData{ + Data: append(d[:0:0], d...), + num: len(d), + } +} + +func (sd *SortableData) Len() int { + return sd.num +} + +func (sd *SortableData) Less(i, j int) bool { + // We assume i, j < len(st.Data) + return sd.Data[i].PricedLine.Line.Time.Before(sd.Data[j].PricedLine.Line.Time) +} + +func (sd *SortableData) Swap(i, j int) { + sd.Data[j], sd.Data[i] = sd.Data[i], sd.Data[j] +}