суббота, 10 октября 2009 г.

Телефонный биллинг: тариф "Направление посекундно"

Публикация имеет двоякий смысл - привести пример задачи, которую удобно выполнять именно на Tcl, а также обеспечить легкий доступ к этой информации для всех разработчиков и пользователей.

Система MBG Billing позволяет администратору системы определять собственные скрипты тарификации, причем они могут хранить правила тарификации непосредственно в своем коде или вызывать любые функции системы, в том числе для обращения к БД.

При старте веб-сервера AOL Web Server загружаются все скрипты тарифов, расположенные в определенной директории. Таким образом, для добавления нового скрипта достаточно положить его в поддиректорию tcl/telephony и перезапустить веб-сервер следующей командой
sudo sv restart billing

Здесь предполагается, что сервис имеет имя billing и управляется супервизором runit.
После этого в веб-интерфейсе можно будет назначать вновь добавленный тариф и устанавливать его параметры.

Скрипт биллингования также грузит все скрипты тарифов из вышеназванной директории. При следующем запуске по расписанию будет обнаружен новый скрипт тарифа и он сможет быть использован при обработке трафика для соответствующих услуг.

Функции скрипта тарифа фиксированы, обеспечивая единый интерфейс вызова любого тарифа. Тариф предоставляет список своих аргументов (args) и их понятное человеку название (argname) и расширенное описание (argdesc), обеспечивает проверку введенных значений аргументов (check) и биллингование (rating) записи с информацией о траффике (звонке). Для аргументов скрипта,требующих выбора значения из списка, функция vals предоставляет набор возможных значений. Внутренние переменные созданы исключительно для удобства программирования и могут быть переименованы или удалены.



############################################
# Copyright 2009, Mobile Business Group
############################################

namespace eval ::telephony::direction_second {
namespace export *

variable type {telephony}
variable name {Направление посекундно}
variable desc {Тарификация посекундная}
variable balance passive

# массив параметров тарифа, значение является списком пар название - описание параметра
# в атрибутах тарифа задается название параметра, например, "Первый тестовый параметр"
variable args {
group_name {
{Направление} {Наименование направления, например, "mtt"}
}
rcode {
{Код типа связи (опционально)} {Один из кодов R40, R1, R2 (зоновая, МГ, МН связь), R1R2 (МНМГ)}
}
}

proc type {} {
variable type
return $type
}
# финансовый счет
proc balance {} {
variable balance
return $balance
}

# идентификатор тарифа, используется как ключ БД
proc id {} {
return [namespace tail [namespace current]]
}

# название тарифа
proc name {} {
variable name
return $name
}
# описание тарифа
proc desc {} {
variable desc
return $desc
}

# список аргументов
proc args {} {
variable args
set arglist {}
foreach {name info} $args {
lappend arglist $name
}
return $arglist
}

# название аргумента
proc argname {arg} {
variable args
array set args_array $args
return [lindex $args_array($arg) 0]
}

# описание аргумента
proc argdesc {arg} {
variable args
array set args_array $args
return [lindex $args_array($arg) 1]
}
# проверка значений аргументов
# проверить значения аргумента
# вернуть пустое значение или описание ошибки
# 1-й аргумент это массив параметров биллингования
proc check {_params} {
variable args
array set args_array $args
array set params $_params
# проверить корректность указания направлений
set groups [::telephony::direction_names]
foreach arg {group_name} {
if {[info exists params($arg)]==0} {
return -code error "Не указан атрибут \"[lindex $args_array($arg) 0]\""
}
if {[lin $groups $params($arg)]==0} {
return -code error "Группа направлений $params($arg) не найдена. Допустимые значения: $groups"
}
}
# проверить код связи (необязательный аргумент)
set arg rcode
if {[info exists params($arg)]==1 && $params($arg) ne {} && [lin {R40 R1 R2 R1R2} $params($arg)]==0} {
return -code error "Код направления $params($arg) не найден. Допустимые значения: R40, R1, R2, R1R2"
}
return
}

# биллинговать запись о звонке
# 1-й аргумент это массив параметров биллингования
# 2-й аргумент это запись о звонке, переданная в виде массива (
# необходимо и достаточно указать 3 параметра, как пример:.
# duration 100 dst 78314604812 date_start "2009-01-11 15:00:01
proc rating {user_service_id _fields counter_month} {
array set fields $_fields
if {$fields(origin) ne {answer}} return
array set params [::billing::user_service::tariff_attributes $user_service_id]

set group $params(group_name)

# по известному направлению и номеру можно определить стоимость минуты разговора
array set out [::telephony:::search_direction $group $fields(dst) $fields(date_start)]
# направление найдено, но звонок по этому направлению не является платным МНМГ вызовом
# и его следует игнорировать
if {[array size out]==0} {
return
}

# если указан код, то тарифицировать только его, а иначе игнорировать
if {[info exists params(rcode)]==0 || $params(rcode) eq {}} {
# код не указан, так что тарифицировать все направления
} elseif {$params(rcode) eq $out(rcode) ||
($params(rcode) eq {R1R2} && ($out(rcode) eq {R1} || $out(rcode) eq {R2}))} {
# звонок по направлению с указанным кодом, тарифицировать
} else {
# код указан, но направление звонка имеет другой код, не тарифицируется
return
}

# тарифицируемая продолжительность
set out(duration) [[namespace current]::duration $fields(duration)]
# стоимость разговора как произведение тарифицируемой продолжительности
# на стоимость минуты по данному направлению
set out(cost) [expr {1.*$out(duration)*$out(price)/60}]
return [array get out]
}

# возвращает длительность звонка согласно правилам тарификации
# продолжительность указывается в секундах
proc duration {duration} {
# посекундная тарификация
return [::telephony::duration_second $duration]
}
}


Функция тарификации rating принимает следующие параметры - идентификатор услуги пользователя, для которой тарифицируется траффик (user_service_id), запись о траффике в виде массива (_fields) и счетчик потребления траффика по услуге за текущий месяц (counter_month). В приведенном выше тарифе счетчик не используется, т.к. стоимость МНМГ звонка не зависит от объема потребления траффика за период. Зато для местных вызовов этот счетчик зачастую необходим, когда за фиксированную абонентскую плату пользователю предоставляется определенное число минут, а по превышении разговоры тарифицируются поминутно.

Комментариев нет:


(C) Alexey Pechnikov aka MBG, mobigroup.ru