package cdr import ( "fmt" "strings" "time" ) func CombineTextCDRs(cdrLines []Line, prices []Price) ([]Text, error) { var err []string numTextCDRs := 0 for _, l := range cdrLines { if l.Kind != TextLine { continue } if l.Reason != ORIG { err = append(err, fmt.Sprintf("text CDR did not match reason ORIG: %+v", l)) continue } numTextCDRs++ } // Now convert each text CDR to a priced Text ret := make([]Text, numTextCDRs) i := 0 for _, l := range cdrLines { if l.Kind != TextLine || l.Reason != ORIG { continue } ret[i] = Text{ PricedLine{ Line: l, Cost: TEXT_OUTGOING_BUY, Price: TEXT_OUTGOING_SELL, }, } i++ } if len(err) > 0 { return nil, fmt.Errorf("error pricing text CDRs: %v", strings.Join(err, ", ")) } return ret, nil } func CombineDataCDRs(cdrLines []Line, prices []Price) ([]Data, error) { var err []string numDataCDRs := 0 errSet := false for _, l := range cdrLines { errSet = false if l.Kind != DataLine { continue } if l.Reason != ORIG { err = append(err, fmt.Sprintf("data CDR did not match reason ORIG: %+v", l)) errSet = true } if l.Destination != DATA_EXPECTED_DESTINATION { err = append(err, fmt.Sprintf("data CDR did not match expected destination %q: %+v", DATA_EXPECTED_DESTINATION, l)) errSet = true } if errSet { continue } numDataCDRs++ } ret := make([]Data, numDataCDRs) i := 0 for _, l := range cdrLines { if l.Kind != DataLine || l.Reason != ORIG || l.Destination != DATA_EXPECTED_DESTINATION { continue } // Initially set cost and price to national buy/sell values dataCost := DATA_OUTGOING_NATIONAL_BUY dataPrice := DATA_OUTGOING_NATIONAL_SELL if l.Source != DATA_NATIONAL_SOURCE { // Roaming CDR // TODO: distinguish between Europe "Roam-like-at-home" and worldwide dataCost = DATA_OUTGOING_ROAMING_BUY dataPrice = DATA_OUTGOING_ROAMING_SELL } dataBytes := l.RawCount dataCost *= dataBytes dataPrice *= dataBytes ret[i] = Data{ PricedLine: PricedLine{ Line: l, Cost: int(dataCost / 1000.0), // compensate for cost specified per MB Price: int(dataPrice / 1000.0), // compensate for price specified per MB }, Bytes: l.RawCount, } i++ } if len(err) > 0 { return nil, fmt.Errorf("error pricing data CDRs: %v", strings.Join(err, ", ")) } return ret, nil } func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) { // Preprocess the price list into a map of "destination" -> Price, filter out the Voice destinations destinationToPrice := make(map[string]Price) destinationToPrice[CALL_NATIONAL_DESTINATION_FIXED.Destination] = CALL_NATIONAL_DESTINATION_FIXED 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 nonMatchedDestinations := make(map[string]struct{}) for _, l := range cdrLines { if l.Kind != VoiceLine { continue } // Cache destination prices _, knownNonPriced := nonMatchedDestinations[l.Destination] if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced { priceLoop: for _, p := range prices { if p.Type != PriceCall { continue priceLoop } if p.Destination == l.Destination { destinationToPrice[l.Destination] = p break priceLoop } nonMatchedDestinations[l.Destination] = struct{}{} } } } for k := range nonMatchedDestinations { fmt.Printf("[warning] no price for destination %v\n", k) } // a map from "%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From 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) if _, ok := uniqueCalls[idx]; !ok { uniqueCalls[idx] = make([]Line, 0) } uniqueCalls[idx] = append(uniqueCalls[idx], l) } ret := make([]Call, len(uniqueCalls)) i := 0 for _, v := range uniqueCalls { /* fmt.Printf("[%s] %d\n", k, len(v)) for i, c := range v { fmt.Printf("\t%d (%d) [%25s]: (%12s): %12s->%12s (%s)\n", i, c.Leg, c.Account, c.CLI, c.From, c.To, c.Reason) } fmt.Println() */ for _, leg := range v { legPrice := Price{} if leg.CLI == leg.From { switch { case leg.Source == CALL_FROM_HANDSET_SOURCE && leg.Destination == CALL_FROM_HANDSET_DESTINATION: // This leg consists of the airtime (outgoing call from the HS perspective) (HS->PBX, non-roaming) legPrice = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET case leg.From != leg.To && leg.Source == CALL_FROM_HANDSET_SOURCE: // Case 2, 3 // This leg consists of routing a call (from a PBX or Handset) through our platform. The costs are defined by the call's destination. // Case 1: PBX->destination // Case 2: HS->destination, non roaming // Case 3: HS->destination, roaming // Case 4: HS->PBX, roaming fallthrough case leg.Source != CALL_FROM_HANDSET_SOURCE && leg.Reason == ORIG: // Case 1 fallthrough case leg.From != leg.To && leg.Source == CALL_FROM_PBX_SOURCE: // Case 4 legPrice = destinationToPrice[leg.Destination] } } if leg.CLI != leg.From { switch { 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 // source -> HS (roaming) case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM: // We only know the destination country fallthrough case leg.CLI == CALL_CLI_ANONYMOUS: // Incoming call from a blocked CLI legPrice = destinationToPrice[leg.Destination] } } if legPrice == (Price{}) { // error, weird category _, 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) } // 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 } */ i++ } fmt.Printf("destinationPrice: %+v\n", destinationToPrice) return nil, fmt.Errorf("not yet implemented") }