FreeSWITCH cgr_computelcr channel variable processing, fix logging of callcosts from session manager, improved tutorial_fs testing

This commit is contained in:
DanB
2015-06-14 19:02:58 +02:00
parent 4957ba1a4a
commit 2d440a046d
13 changed files with 135 additions and 184 deletions

View File

@@ -7,65 +7,9 @@
// This is what you get when you load CGRateS with an empty configuration file.
//"general": {
// "http_skip_tls_veify": false, // if enabled Http Client will accept any TLS certificate
// "rounding_decimals": 10, // system level precision for floats
// "dbdata_encoding": "msgpack", // encoding used to store object data in strings: <msgpack|json>
// "tpexport_dir": "/var/log/cgrates/tpe", // path towards export folder for offline Tariff Plans
// "default_reqtype": "*rated", // default request type to consider when missing from requests: <""|*prepaid|*postpaid|*pseudoprepaid|*rated>
// "default_category": "call", // default Type of Record to consider when missing from requests
// "default_tenant": "cgrates.org", // default Tenant to consider when missing from requests
// "default_subject": "cgrates", // default rating Subject to consider when missing from requests
//},
//"listen": {
// "rpc_json": "127.0.0.1:2012", // RPC JSON listening address
// "rpc_gob": "127.0.0.1:2013", // RPC GOB listening address
// "http": "127.0.0.1:2080", // HTTP listening address
//},
//"rating_db": {
// "db_type": "redis", // rating subsystem database type: <redis>
// "db_host": "127.0.0.1", // rating subsystem database host address
// "db_port": 6379, // rating subsystem port to reach the database
// "db_name": "10", // rating subsystem database name to connect to
// "db_user": "", // rating subsystem username to use when connecting to database
// "db_passwd": "", // rating subsystem password to use when connecting to database
//},
//"accounting_db": {
// "db_type": "redis", // accounting subsystem database: <redis>
// "db_host": "127.0.0.1", // accounting subsystem database host address
// "db_port": 6379, // accounting subsystem port to reach the database
// "db_name": "11", // accounting subsystem database name to connect to
// "db_user": "", // accounting subsystem username to use when connecting to database
// "db_passwd": "", // accounting subsystem password to use when connecting to database
//},
//"stor_db": {
// "db_type": "mysql", // stor database type to use: <mysql|postgres>
// "db_host": "127.0.0.1", // the host to connect to
// "db_port": 3306, // the port to reach the stordb
// "db_name": "cgrates", // stor database name
// "db_user": "cgrates", // username to use when connecting to stordb
// "db_passwd": "CGRateS.org", // password to use when connecting to stordb
// "max_open_conns": 0, // maximum database connections opened
// "max_idle_conns": -1, // maximum database connections idle
//},
//"balancer": {
// "enabled": false, // start Balancer service: <true|false>
//},
"rater": {
"enabled": true, // enable Rater service: <true|false>
// "balancer": "", // register to Balancer as worker: <""|internal|x.y.z.y:1234>
"cdrstats": "internal", // address where to reach the cdrstats service, empty to disable stats functionality<""|internal|x.y.z.y:1234>
},
@@ -76,36 +20,12 @@
"cdrs": {
"enabled": true, // start the CDR Server service: <true|false>
// "extra_fields": [], // extra fields to store in CDRs for non-generic CDRs
// "rate_cdrs": true, // enable CDR rating calculation
// "store_cdrs": true, // store cdrs in storDb
"rater": "internal", // address where to reach the Rater for cost calculation: <""|internal|x.y.z.y:1234>
"cdrstats": "internal", // address where to reach the cdrstats service. Empty to disable stats gathering <""|internal|x.y.z.y:1234>
// "reconnects": 5, // number of reconnect attempts to rater or cdrs
// "cdr_replication":[], // replicate the raw CDR to a number of servers
},
"cdrstats": {
"enabled": true, // starts the cdrstats service: <true|false>
// "queue_length": 50, // number of items in the stats buffer
// "time_window": "1h", // will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
// "metrics": ["ASR", "ACD", "ACC"], // stat metric ids to build
// "setup_interval": [], // filter on CDR SetupTime
// "tors": [], // filter on CDR TOR fields
// "cdr_hosts": [], // filter on CDR CdrHost fields
// "cdr_sources": [], // filter on CDR CdrSource fields
// "req_types": [], // filter on CDR ReqType fields
// "directions": [], // filter on CDR Direction fields
// "tenants": [], // filter on CDR Tenant fields
// "categories": [], // filter on CDR Category fields
// "accounts": [], // filter on CDR Account fields
// "subjects": [], // filter on CDR Subject fields
// "destination_prefixes": [], // filter on CDR Destination prefixes
// "usage_interval": [], // filter on CDR Usage
// "mediation_run_ids": [], // filter on CDR MediationRunId fields
// "rated_accounts": [], // filter on CDR RatedAccount fields
// "rated_subjects": [], // filter on CDR RatedSubject fields
// "cost_interval": [], // filter on CDR Cost
},
@@ -145,103 +65,27 @@
},
//"cdrc": {
// "*default": {
// "enabled": false, // enable CDR client functionality
// "cdrs_address": "internal", // address where to reach CDR server. <internal|x.y.z.y:1234>
// "cdr_format": "csv", // CDR file format <csv|freeswitch_csv|fwv>
// "field_separator": ",", // separator used in case of csv files
// "run_delay": 0, // sleep interval in seconds between consecutive runs, 0 to use automation via inotify
// "data_usage_multiply_factor": 1024, // conversion factor for data usage
// "cdr_in_dir": "/var/log/cgrates/cdrc/in", // absolute path towards the directory where the CDRs are stored
// "cdr_out_dir": "/var/log/cgrates/cdrc/out", // absolute path towards the directory where processed CDRs will be moved
// "cdr_source_id": "freeswitch_csv", // free form field, tag identifying the source of the CDRs within CDRS database
// "cdr_filter": "", // Filter CDR records to import
// "cdr_fields":[ // import template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value
// {"tag": "tor", "cdr_field_id": "tor", "type": "cdrfield", "value": "2", "mandatory": true},
// {"tag": "accid", "cdr_field_id": "accid", "type": "cdrfield", "value": "3", "mandatory": true},
// {"tag": "reqtype", "cdr_field_id": "reqtype", "type": "cdrfield", "value": "4", "mandatory": true},
// {"tag": "direction", "cdr_field_id": "direction", "type": "cdrfield", "value": "5", "mandatory": true},
// {"tag": "tenant", "cdr_field_id": "tenant", "type": "cdrfield", "value": "6", "mandatory": true},
// {"tag": "category", "cdr_field_id": "category", "type": "cdrfield", "value": "7", "mandatory": true},
// {"tag": "account", "cdr_field_id": "account", "type": "cdrfield", "value": "8", "mandatory": true},
// {"tag": "subject", "cdr_field_id": "subject", "type": "cdrfield", "value": "9", "mandatory": true},
// {"tag": "destination", "cdr_field_id": "destination", "type": "cdrfield", "value": "10", "mandatory": true},
// {"tag": "setup_time", "cdr_field_id": "setup_time", "type": "cdrfield", "value": "11", "mandatory": true},
// {"tag": "answer_time", "cdr_field_id": "answer_time", "type": "cdrfield", "value": "12", "mandatory": true},
// {"tag": "usage", "cdr_field_id": "usage", "type": "cdrfield", "value": "13", "mandatory": true},
// ],
// }
//},
"sm_freeswitch": {
"enabled": true, // starts SessionManager service: <true|false>
"rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013>
"cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234>
// "reconnects": 5, // number of reconnect attempts to rater or cdrs
"create_cdr": true, // create CDR out of events and sends them to CDRS component
// "cdr_extra_fields": [], // extra fields to store in CDRs in case of processing them
"debit_interval": "5s", // interval to perform debits on.
// "min_call_duration": "0s", // only authorize calls with allowed duration higher than this
// "max_call_duration": "3h", // maximum call duration a prepaid call can last
// "min_dur_low_balance": "5s", // threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval)
// "low_balance_ann_file": "", // file to be played when low balance is reached for prepaid calls
// "empty_balance_context": "", // if defined, prepaid calls will be transfered to this context on empty balance
// "empty_balance_ann_file": "", // file to be played before disconnecting prepaid calls on empty balance (applies only if no context defined)
"connections":[ // instantiate connections to multiple FreeSWITCH servers
{"server": "127.0.0.1:8021", "password": "ClueCon", "reconnects": 15}
],
},
//"sm_kamailio": {
// "enabled": false, // starts SessionManager service: <true|false>
// "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013>
// "cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234>
// "reconnects": 5, // number of reconnect attempts to rater or cdrs
// "debit_interval": "10s", // interval to perform debits on.
// "min_call_duration": "0s", // only authorize calls with allowed duration higher than this
// "max_call_duration": "3h", // maximum call duration a prepaid call can last
// "connections":[ // instantiate connections to multiple Kamailio servers
// {"evapi_addr": "127.0.0.1:8448", "reconnects": -1} // reconnects -1 to indefinitely connect
// ],
//},
//"sm_opensips": {
// "enabled": false, // starts SessionManager service: <true|false>
// "listen_udp": "127.0.0.1:2020", // address where to listen for datagram events coming from OpenSIPS
// "rater": "internal", // address where to reach the Rater <""|internal|127.0.0.1:2013>
// "cdrs": "internal", // address where to reach CDR Server, empty to disable CDR capturing <""|internal|x.y.z.y:1234>
// "debit_interval": "10s", // interval to perform debits on.
// "min_call_duration": "0s", // only authorize calls with allowed duration higher than this
// "max_call_duration": "3h", // maximum call duration a prepaid call can last
// "events_subscribe_interval": "60s", // automatic events subscription to OpenSIPS, 0 to disable it
// "mi_addr": "127.0.0.1:8020", // address where to reach OpenSIPS MI to send session disconnects
// "reconnects": -1, // reconnects -1 to indefinitely connect
//},
"history_server": {
"enabled": true, // starts History service: <true|false>.
"history_dir": "/tmp/cgr_fsevsock/cgrates/history", // location on disk where to store history files.
// "save_interval": "1s", // interval to save changed cache into .git archive
},
"history_agent": {
"enabled": true, // starts History as a client: <true|false>.
// "server": "internal", // address where to reach the master history server: <internal|x.y.z.y:1234>
},
//"mailer": {
// "server": "localhost", // the server to use when sending emails out
// "auth_user": "cgrates", // authenticate to email server using this user
// "auth_passwd": "CGRateS.org", // authenticate to email server with this password
// "from_address": "cgr-mailer@localhost.localdomain" // from address used when sending emails out
//},
}

View File

@@ -108,9 +108,10 @@ type CallCostLog struct {
// RPC method, used to log callcosts to db
func (self *CdrServer) LogCallCost(ccl *CallCostLog) error {
Logger.Debug(fmt.Sprintf("LogCallCost, callCostLog: %+v, cost: %+v", ccl, ccl.CallCost))
if ccl.CheckDuplicate {
cc, err := self.cdrDb.GetCallCostLog(ccl.CgrId, ccl.Source, ccl.RunId)
if err != nil {
if err != nil && err.Error() != "record not found" {
return err
}
if cc != nil {
@@ -245,7 +246,7 @@ func (self *CdrServer) getCostFromRater(storedCdr *StoredCdr) (*CallCost, error)
}
if utils.IsSliceMember([]string{utils.META_PSEUDOPREPAID, utils.META_POSTPAID, utils.PSEUDOPREPAID, utils.POSTPAID}, storedCdr.ReqType) {
if err = self.rater.Debit(cd, cc); err == nil { // Debit has occured, we are forced to write the log, even if CDR store is disabled
self.cdrDb.LogCallCost(storedCdr.CgrId, MEDIATOR_SOURCE, storedCdr.MediationRunId, cc)
self.cdrDb.LogCallCost(storedCdr.CgrId, utils.CDRS_SOURCE, storedCdr.MediationRunId, cc)
}
} else {
err = self.rater.GetCost(cd, cc)

View File

@@ -51,4 +51,5 @@ type Event interface {
AsStoredCdr() *StoredCdr
String() string
AsEvent(string) Event
ComputeLcr() bool
}

View File

@@ -305,21 +305,36 @@ func (lc *LCRCost) LogErrors() {
}
}
func (lc *LCRCost) SuppliersSlice() ([]string, error) {
if lc.Entry == nil {
return nil, utils.ErrNotFound
}
supps := []string{}
for _, supplCost := range lc.SupplierCosts {
if dtcs, err := utils.NewDTCSFromRPKey(supplCost.Supplier); err != nil {
return nil, err
} else if len(dtcs.Subject) != 0 {
supps = append(supps, dtcs.Subject)
}
}
if len(supps) == 0 {
return nil, utils.ErrNotFound
}
return supps, nil
}
// Returns a list of suppliers separated via
func (lc *LCRCost) SuppliersString() (string, error) {
supplStr := ""
if lc.Entry == nil {
return "", utils.ErrNotFound
supps, err := lc.SuppliersSlice()
if err != nil {
return "", err
}
for idx, supplCost := range lc.SupplierCosts {
if dtcs, err := utils.NewDTCSFromRPKey(supplCost.Supplier); err != nil {
return "", err
} else {
if idx != 0 {
supplStr += utils.FIELDS_SEP
}
supplStr += dtcs.Subject
supplStr := ""
for idx, suppl := range supps {
if idx != 0 {
supplStr += utils.FIELDS_SEP
}
supplStr += suppl
}
return supplStr, nil
}

View File

@@ -234,6 +234,27 @@ func TestLcrRequestAsCallDescriptor(t *testing.T) {
}
}
func TestLCRCostSuppliersSlice(t *testing.T) {
lcrCost := new(LCRCost)
if _, err := lcrCost.SuppliersString(); err == nil || err != utils.ErrNotFound {
t.Errorf("Unexpected error received: %v", err)
}
lcrCost = &LCRCost{
Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_STATIC, StrategyParams: "ivo12;dan12;rif12", Weight: 10.0},
SupplierCosts: []*LCRSupplierCost{
&LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 1.8, Duration: 60 * time.Second},
&LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second},
&LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 1.2, Duration: 60 * time.Second},
},
}
eSuppls := []string{"ivo12", "dan12", "rif12"}
if suppls, err := lcrCost.SuppliersSlice(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eSuppls, suppls) {
t.Errorf("Expecting: %+v, received: %+v", eSuppls, suppls)
}
}
func TestLCRCostSuppliersString(t *testing.T) {
lcrCost := new(LCRCost)
if _, err := lcrCost.SuppliersString(); err == nil || err != utils.ErrNotFound {

View File

@@ -418,6 +418,9 @@ func (storedCdr *StoredCdr) AsExternalCdr() *ExternalCdr {
func (storedCdr *StoredCdr) AsEvent(ignored string) Event {
return Event(storedCdr)
}
func (storedCdr *StoredCdr) ComputeLcr() bool {
return false
}
func (storedCdr *StoredCdr) GetName() string {
return storedCdr.CdrSource
}

View File

@@ -291,6 +291,9 @@ func TestTutFsCallsCdrs(t *testing.T) {
if reply[0].Usage != "67" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Cost == -1.0 { // Cost was not calculated
t.Errorf("Unexpected Cost for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1001"}, RunIds: []string{"derived_run1"}, FilterOnRated: true}
if err := tutFsCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -380,6 +383,9 @@ func TestTutFsCallsCdrs(t *testing.T) {
if reply[0].Usage != "64" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Cost == -1.0 { // Cost was not calculated
t.Errorf("Unexpected Cost for CDR: %+v", reply[0])
}
}
req = utils.RpcCdrsFilter{Accounts: []string{"1007"}, RunIds: []string{utils.META_DEFAULT}}
if err := tutFsCallsRpc.Call("ApierV2.GetCdrs", req, &reply); err != nil {
@@ -399,6 +405,9 @@ func TestTutFsCallsCdrs(t *testing.T) {
if reply[0].Usage != "66" { // Usage as seconds
t.Errorf("Unexpected Usage for CDR: %+v", reply[0])
}
if reply[0].Cost == -1.0 { // Cost was not calculated
t.Errorf("Unexpected Cost for CDR: %+v", reply[0])
}
}
}

View File

@@ -72,6 +72,7 @@ const (
IGNOREPARK = "variable_cgr_ignorepark"
VAR_CGR_DISCONNECT_CAUSE = "variable_" + utils.CGR_DISCONNECT_CAUSE
VAR_CGR_CMPUTELCR = "variable_" + utils.CGR_COMPUTELCR
)
// Nice printing for the event object.
@@ -362,7 +363,30 @@ func (fsev FSEvent) AsStoredCdr() *engine.StoredCdr {
return storCdr
}
// Converts a slice of strings into a FS array string
func (fsev FSEvent) ComputeLcr() bool {
if computeLcr, err := strconv.ParseBool(fsev[VAR_CGR_CMPUTELCR]); err != nil {
return false
} else {
return computeLcr
}
}
// Converts into CallDescriptor due to responder interface needs
func (fsev FSEvent) AsCallDescriptor() (*engine.CallDescriptor, error) {
lcrReq := &engine.LcrRequest{
Direction: fsev.GetDirection(utils.META_DEFAULT),
Tenant: fsev.GetTenant(utils.META_DEFAULT),
Category: fsev.GetCategory(utils.META_DEFAULT),
Account: fsev.GetAccount(utils.META_DEFAULT),
Subject: fsev.GetSubject(utils.META_DEFAULT),
Destination: fsev.GetDestination(utils.META_DEFAULT),
StartTime: utils.FirstNonEmpty(fsev[SETUP_TIME], fsev[ANSWER_TIME]),
Duration: fsev[DURATION],
}
return lcrReq.AsCallDescriptor()
}
// Converts a slice of strings into a FS array string, contains len(array) at first index since FS does not support len(ARRAY::) for now
func SliceAsFsArray(slc []string) string {
arry := ""
if len(slc) == 0 {
@@ -370,7 +394,7 @@ func SliceAsFsArray(slc []string) string {
}
for idx, itm := range slc {
if idx == 0 {
arry += "ARRAY::" + itm
arry = fmt.Sprintf("ARRAY::%d|:%s", len(slc), itm)
} else {
arry += "|:" + itm
}

View File

@@ -665,7 +665,7 @@ func TestSliceAsFsArray(t *testing.T) {
t.Error(fsArray)
}
items = []string{"item1", "item2", "item3"}
if fsArray := SliceAsFsArray(items); fsArray != "ARRAY::item1|:item2|:item3" {
if fsArray := SliceAsFsArray(items); fsArray != "ARRAY::3|:item1|:item2|:item3" {
t.Error(fsArray)
}
}

View File

@@ -207,15 +207,36 @@ func (sm *FSSessionManager) onChannelPark(ev engine.Event, connId string) {
return
}
sm.setMaxCallDuration(ev.GetUUID(), connId, maxCallDur)
/*if sm.cfg.ComputeLcr { // Fix here out of channel variable
if err := sm.setCgrLcr(ev, connId); err != nil {
engine.Logger.Err(fmt.Sprintf("<SM-FreeSWITCH> Could not set LCR for %s, error: %s", ev.GetUUID(), err.Error()))
// ComputeLcr
if ev.ComputeLcr() {
cd, err := fsev.AsCallDescriptor()
if err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-FreeSWITCH> LCR_PREPROCESS_ERROR: %s", err.Error()))
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR)
return
}
var lcr engine.LCRCost
if err = sm.Rater().GetLCR(cd, &lcr); err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-FreeSWITCH> LCR_API_ERROR: %s", err.Error()))
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR)
}
if lcr.HasErrors() {
lcr.LogErrors()
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR)
return
}
if supps, err := lcr.SuppliersSlice(); err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-FreeSWITCH> LCR_ERROR: %s", err.Error()))
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR)
return
} else {
fsArray := SliceAsFsArray(supps)
if _, err = sm.conns[connId].SendApiCmd(fmt.Sprintf("uuid_setvar %s %s %s\n\n", ev.GetUUID(), utils.CGR_SUPPLIERS, fsArray)); err != nil {
engine.Logger.Info(fmt.Sprintf("<SM-FreeSWITCH> LCR_ERROR: %s", err.Error()))
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), SYSTEM_ERROR)
}
}
}
*/
sm.unparkCall(ev.GetUUID(), connId, ev.GetCallDestNr(utils.META_DEFAULT), AUTH_OK)
}

View File

@@ -228,11 +228,6 @@ func (kev KamEvent) GetCdrSource() string {
return "KAMAILIO_" + kev.GetName()
}
func (kev KamEvent) ComputeLcr() bool {
compute, _ := strconv.ParseBool(kev[utils.CGR_COMPUTELCR])
return compute
}
func (kev KamEvent) MissingParameter() bool {
var nullTime time.Time
switch kev.GetName() {
@@ -391,3 +386,11 @@ func (kev KamEvent) AsCallDescriptor() (*engine.CallDescriptor, error) {
}
return lcrReq.AsCallDescriptor()
}
func (kev KamEvent) ComputeLcr() bool {
if computeLcr, err := strconv.ParseBool(kev[utils.CGR_COMPUTELCR]); err != nil {
return false
} else {
return computeLcr
}
}

View File

@@ -20,6 +20,7 @@ package sessionmanager
import (
"encoding/json"
"strconv"
"strings"
"time"
@@ -298,3 +299,11 @@ func (osipsEv *OsipsEvent) updateDurationFromEvent(updatedOsipsEv *OsipsEvent) e
osipsEv.osipsEvent.AttrValues[OSIPS_SIPCODE] = updatedOsipsEv.osipsEvent.AttrValues[OSIPS_SIPCODE]
return nil
}
func (osipsEv *OsipsEvent) ComputeLcr() bool {
if computeLcr, err := strconv.ParseBool(osipsEv.osipsEvent.AttrValues[utils.CGR_COMPUTELCR]); err != nil {
return false
} else {
return computeLcr
}
}

View File

@@ -172,7 +172,6 @@ const (
LOG_CDR = "cdr_"
LOG_MEDIATED_CDR = "mcd_"
SESSION_MANAGER_SOURCE = "SMR"
MEDIATOR_SOURCE = "MED"
CDRS_SOURCE = "CDRS"
SCHED_SOURCE = "SCH"
RATER_SOURCE = "RAT"
@@ -189,6 +188,7 @@ const (
CGR_AUTHORIZE = "CGR_AUTHORIZE"
CONFIG_DIR = "/etc/cgrates/"
CGR_SUPPLIER = "cgr_supplier"
CGR_SUPPLIERS = "cgr_suppliers"
DISCONNECT_CAUSE = "disconnect_cause"
CGR_DISCONNECT_CAUSE = "cgr_disconnectcause"
CGR_COMPUTELCR = "cgr_computelcr"