From aabd020937ff9fd74603310fb30d3adb6e5dd798 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 4 Dec 2015 15:12:56 -0500 Subject: [PATCH] MT#14457 re-implement onpeak/offpeak CDR fragmentation Change-Id: Ib4284d468dd441f5d3bb7f97291eb8ae9bfef4df --- rate-o-mat.pl | 93 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/rate-o-mat.pl b/rate-o-mat.pl index 8485730..85fdd50 100755 --- a/rate-o-mat.pl +++ b/rate-o-mat.pl @@ -21,6 +21,11 @@ my $log_ident = 'rate-o-mat'; my $log_facility = 'daemon'; my $log_opts = 'ndely,cons,pid,nowait'; +# if split_peak_parts is set to true, rate-o-mat will create a separate +# CDR every time a peak time border is crossed for either the customer, +# the reseller or the carrier billing profile. +my $split_peak_parts = 0; + # if the LNP database is used not just for LNP, but also for on-net # billing, special routing or similar things, this should be set to # better guess the correct LNP provider ID when selecting ported numbers @@ -53,6 +58,8 @@ my $DupDB_Port = $ENV{RATEOMAT_DUPLICATE_DB_PORT} ? int $ENV{RATEOMAT_DUPLICATE_ my $DupDB_User = $ENV{RATEOMAT_DUPLICATE_DB_USER}; my $DupDB_Pass = $ENV{RATEOMAT_DUPLICATE_DB_PASS}; +$split_peak_parts = $ENV{RATEOMAT_SPLIT_PEAK_PARTS} // $split_peak_parts; + ######################################################################## sub main; @@ -74,6 +81,7 @@ my $sth_offpeak_weekdays; my $sth_offpeak_special; my $sth_unrated_cdrs; my $sth_update_cdr; +my $sth_create_cdr_fragment; my $sth_provider_info; my $sth_reseller_info; my $sth_get_cbalances; @@ -478,17 +486,36 @@ EOS "destination_carrier_cost = ?, destination_reseller_cost = ?, destination_customer_cost = ?, ". "destination_carrier_free_time = ?, destination_reseller_free_time = ?, destination_customer_free_time = ?, ". "destination_carrier_billing_fee_id = ?, destination_reseller_billing_fee_id = ?, destination_customer_billing_fee_id = ?, ". - "destination_carrier_billing_zone_id = ?, destination_reseller_billing_zone_id = ?, destination_customer_billing_zone_id = ? ". + "destination_carrier_billing_zone_id = ?, destination_reseller_billing_zone_id = ?, destination_customer_billing_zone_id = ?, ". + "frag_carrier_onpeak = ?, frag_reseller_onpeak = ?, frag_customer_onpeak = ?, is_fragmented = ?, ". + "duration = ? ". "WHERE id = ?" ) or FATAL "Error preparing update cdr statement: ".$acctdbh->errstr; + if ($split_peak_parts) { + my @exclude_fragment_fields = qw(start_time duration is_fragmented); + my %exclude_fragment_fields = map {$_,1} @exclude_fragment_fields; + my @fragment_fields = grep {!$exclude_fragment_fields{$_}} @cdr_fields; + $sth_create_cdr_fragment = $acctdbh->prepare( + "INSERT INTO accounting.cdr + (". + join(',', @fragment_fields, @exclude_fragment_fields). + ") + SELECT ". + join(',', @fragment_fields).", + start_time + ?,duration - ?,1 + FROM accounting.cdr + WHERE id = ? + ") or FATAL "Error preparing create cdr fragment statement: ".$acctdbh->errstr; + } + $sth_provider_info = $billdbh->prepare( "SELECT p.class, bm.billing_profile_id ". "FROM billing.products p, billing.billing_mappings bm ". "WHERE bm.contract_id = ? AND bm.product_id = p.id ". "AND (bm.start_date IS NULL OR bm.start_date <= FROM_UNIXTIME(?)) ". "AND (bm.end_date IS NULL OR bm.end_date >= FROM_UNIXTIME(?)) ". - "ORDER BY bm.start_date, bm.id DESC ". + "ORDER BY bm.start_date DESC, bm.id DESC ". "LIMIT 1" ) or FATAL "Error preparing provider info statement: ".$billdbh->errstr; @@ -502,7 +529,7 @@ EOS "AND r.contract_id = bm.contract_id ". "AND (bm.start_date IS NULL OR bm.start_date <= FROM_UNIXTIME(?)) ". "AND (bm.end_date IS NULL OR bm.end_date >= FROM_UNIXTIME(?)) ". - "ORDER BY bm.start_date, bm.id DESC ". + "ORDER BY bm.start_date DESC, bm.id DESC ". "LIMIT 1" ) or FATAL "Error preparing reseller info statement: ".$billdbh->errstr; @@ -1478,6 +1505,8 @@ sub update_cdr $cdr->{destination_carrier_free_time}, $cdr->{destination_reseller_free_time}, $cdr->{destination_customer_free_time}, $cdr->{destination_carrier_billing_fee_id}, $cdr->{destination_reseller_billing_fee_id}, $cdr->{destination_customer_billing_fee_id}, $cdr->{destination_carrier_billing_zone_id}, $cdr->{destination_reseller_billing_zone_id}, $cdr->{destination_customer_billing_zone_id}, + $cdr->{frag_carrier_onpeak}, $cdr->{frag_reseller_onpeak}, $cdr->{frag_customer_onpeak}, + $cdr->{is_fragmented} // 0, $cdr->{duration}, $cdr->{id}) or FATAL "Error executing update cdr statement: ".$sth->errstr; @@ -1545,8 +1574,6 @@ sub get_call_cost my $r_onpeak = shift; my $r_balances = shift; - $$r_rating_duration = 0; # ensure we start with zero length - my $src_user = $cdr->{source_cli}; my $src_user_domain = $cdr->{source_cli}.'@'.$cdr->{source_domain}; my $dst_user = $cdr->{destination_user_in}; @@ -1574,6 +1601,8 @@ sub get_call_cost } } + $$r_rating_duration = 0; # ensure we start with zero length + DEBUG "billing fee is ".(Dumper $r_profile_info); #print Dumper $r_profile_info; @@ -1597,8 +1626,8 @@ sub get_call_cost my $rate = 0; my $offset = 0; my $onpeak = 0; - my $init = 0; - my $duration = $cdr->{duration}; + my $init = $cdr->{is_fragmented} // 0; + my $duration = (defined $cdr->{rating_duration} and $cdr->{rating_duration} < $cdr->{duration}) ? $cdr->{rating_duration} : $cdr->{duration}; my $prev_bal_id = undef; my @cash_balance_rates = (); my $prev_cash_balance = undef; @@ -1645,17 +1674,20 @@ sub get_call_cost $r_profile_info->{on_init_interval} : $r_profile_info->{off_init_interval}; $rate = $onpeak == 1 ? $r_profile_info->{on_init_rate} : $r_profile_info->{off_init_rate}; - $$r_onpeak = $onpeak; DEBUG "add init rate $rate per sec to costs"; } else { + last if $split_peak_parts and defined($$r_onpeak) and $$r_onpeak != $onpeak + and !defined $cdr->{rating_duration}; + $interval = $onpeak == 1 ? $r_profile_info->{on_follow_interval} : $r_profile_info->{off_follow_interval}; $rate = $onpeak == 1 ? $r_profile_info->{on_follow_rate} : $r_profile_info->{off_follow_rate}; DEBUG "add follow rate $rate per sec to costs"; } + $$r_onpeak = $onpeak; $rate *= $interval; DEBUG "interval is $interval, so rate for this interval is $rate"; @@ -1818,6 +1850,7 @@ sub get_customer_call_cost $cdr->{$dir."customer_billing_fee_id"} = $profile_info{fee_id}; $cdr->{$dir."customer_billing_zone_id"} = $profile_info{zone_id}; + $cdr->{frag_customer_onpeak} = $onpeak if $split_peak_parts; DEBUG "got call cost $$r_cost and free time $$r_free_time"; # we don't do prepaid for termination fees for now, so treat it as post-paid @@ -1911,6 +1944,7 @@ sub get_provider_call_cost $cdr->{destination_reseller_billing_fee_id} = $profile_info{fee_id}; $cdr->{destination_reseller_billing_zone_id} = $profile_info{zone_id}; } + $cdr->{frag_reseller_onpeak} = $onpeak if $split_peak_parts; } else { @@ -1921,6 +1955,7 @@ sub get_provider_call_cost $cdr->{destination_carrier_billing_fee_id} = $profile_info{fee_id}; $cdr->{destination_carrier_billing_zone_id} = $profile_info{zone_id}; } + $cdr->{frag_carrier_onpeak} = $onpeak if $split_peak_parts; } return 1; @@ -1945,7 +1980,7 @@ sub rate_cdr my $destination_reseller_free_time = 0; my $direction; - my $rating_duration; + my @rating_durations; unless($cdr->{call_status} eq "ok") { @@ -2014,7 +2049,7 @@ sub rate_cdr DEBUG "destination provider has billing profile $destination_provider_info{profile_id}, get reseller termination cost"; get_provider_call_cost($cdr, $type, "in", \%destination_provider_info, \$destination_reseller_cost, \$destination_reseller_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting destination reseller cost for local destination_provider_id ". $cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n"; DEBUG "destination reseller termination cost is $destination_reseller_cost"; @@ -2026,7 +2061,7 @@ sub rate_cdr DEBUG "get customer termination cost for destination_user_id $$cdr{destination_user_id}"; get_customer_call_cost($cdr, $type, "in", \$destination_customer_cost, \$destination_customer_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting destination customer cost for local destination_user_id ". $cdr->{destination_user_id}." for cdr ".$cdr->{id}."\n"; DEBUG "destination customer termination cost is $destination_customer_cost"; @@ -2040,7 +2075,7 @@ sub rate_cdr DEBUG "fetching source_carrier_cost based on destination_provider_info ".(Dumper \%destination_provider_info); get_provider_call_cost($cdr, $type, "out", \%destination_provider_info, \$source_carrier_cost, \$source_carrier_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting source carrier cost for cdr ".$cdr->{id}."\n"; } else { WARNING "missing destination profile, so we can't calculate source_carrier_cost for destination_provider_info ".(Dumper \%destination_provider_info); @@ -2051,7 +2086,7 @@ sub rate_cdr if($source_provider_info{profile_id}) { get_provider_call_cost($cdr, $type, "out", \%source_provider_info, \$source_reseller_cost, \$source_reseller_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting source reseller cost for cdr ".$cdr->{id}."\n"; } else { # up to 2.8, there is one hardcoded reseller id 1, which doesn't have a billing profile, so skip this step here. @@ -2061,7 +2096,7 @@ sub rate_cdr # get customer cost get_customer_call_cost($cdr, $type, "out", \$source_customer_cost, \$source_customer_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting source customer cost for local source_user_id ". $cdr->{source_user_id}." for cdr ".$cdr->{id}."\n"; } else { @@ -2080,7 +2115,7 @@ sub rate_cdr DEBUG "fetching destination_carrier_cost based on source_provider_info ".(Dumper \%source_provider_info); get_provider_call_cost($cdr, $type, "in", \%source_provider_info, \$destination_carrier_cost, \$destination_carrier_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting destination carrier cost for local destination_provider_id ". $cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n"; } else { @@ -2090,7 +2125,7 @@ sub rate_cdr DEBUG "fetching destination_reseller_cost based on source_provider_info ".(Dumper \%destination_provider_info); get_provider_call_cost($cdr, $type, "in", \%destination_provider_info, \$destination_reseller_cost, \$destination_reseller_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting destination reseller cost for local destination_provider_id ". $cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n"; } else { @@ -2100,7 +2135,7 @@ sub rate_cdr } get_customer_call_cost($cdr, $type, "in", \$destination_customer_cost, \$destination_customer_free_time, - \$rating_duration) + \$rating_durations[@rating_durations]) or FATAL "Error getting destination customer cost for local destination_user_id ". $cdr->{destination_user_id}." for cdr ".$cdr->{id}."\n"; } else { @@ -2108,6 +2143,29 @@ sub rate_cdr } } + if ($split_peak_parts) { + # We require the onpeak/offpeak thresholds to be the same for all rating fee profiles used by any + # one particular CDR, so that CDR fragmentations are uniform across customer/carrier/reseller/etc + # entries. Mismatching onpeak/offpeak thresholds are a fatal error (which also results in a + # transaction rollback). + + my %rating_durations; + for my $rd (@rating_durations) { + defined($rd) and $rating_durations{$rd} = 1; + } + scalar(keys(%rating_durations)) > 1 + and FATAL "Error getting consistent rating fragment for cdr ".$cdr->{id}.". Rating profiles don't match."; + my $rating_duration = (keys(%rating_durations))[0] // $cdr->{duration}; + + if ($rating_duration < $cdr->{duration}) { + my $sth = $sth_create_cdr_fragment; + $sth->execute($rating_duration, $rating_duration, $cdr->{id}) + or FATAL "Error executing create cdr fragment statement: ".$sth->errstr; + $cdr->{is_fragmented} = 1; + $cdr->{duration} = $rating_duration; + } + } + $cdr->{source_carrier_cost} = $source_carrier_cost; $cdr->{source_reseller_cost} = $source_reseller_cost; $cdr->{source_customer_cost} = $source_customer_cost; @@ -2289,6 +2347,7 @@ sub main $sth_offpeak_special->finish; $sth_unrated_cdrs->finish; $sth_update_cdr->finish; + $split_peak_parts and $sth_create_cdr_fragment->finish; $sth_provider_info->finish; $sth_reseller_info->finish; $sth_get_cbalances->finish;