libphonenumber: una libreria per decodificare i numeri telefonici dei CDR

Alle prese con lo sviluppo di un software di elaborazione per i CDR che sputano fuori gli MD110 ancora in dotazione all’Università di Siena, mi trovo nella necessità di eseguire una analisi il più possibile precisa delle numerazioni.

In particolare, lavorando con il listino CONSIP Telefonia Fissa sotto mano, ho la necessità di diversificare tra:

  • chiamate urbane
  • chiamate interurbane
  • chiamate verso numeri mobili (“cellulari”)
  • chiamate internazionali
  • chiamate verso numeri verdi
  • chiamate verso numeri a tariffazione speciale

Come vedete, il panorama è piuttosto variegato così, dopo una ricerca su Google, scopro che partendo da una libreria Java messa a disposizione proprio da “Big G” (Google’s common Java, C++ and Javascript library for parsing, formatting, storing and validating international phone numbers), sono state create librerie in PHP con funzioni decisamente interessanti, di cui voglio parlarvi in questo post.

Dopo averne provate alcune, essenzialmente simili, mi sono fermato con la più aggiornata, libphonenumber-for-php, che trovate su Github pronta per essere scaricata ed usata. Tanto per rendervi conto delle potenzialità, fate un salto sulla demo: giggsey.com/libphonenumber/

Se la cosa vi incuriosisce, approfondiamo il tutto partendo da una semplice riga:

   07312246 0008       M      011111           05219xxxxx 47xx   00 051002047
 N.B. Ho oscurato gli ultimi numeri, per motivi di privacy

Questa serie di numeri e lettere, all’apparenza insignificanti, descrive una telefonata secondo il formato configurato sugli MD110 che mi trovo a gestire.

Le prime 4 cifre sono la data in formato MMDD, le seguenti 4 l’ora nel formato HHMM. Pertanto questa è una telefonata delle ore 22:46 del 31 luglio. Il secondo gruppo di 4 cifre è la durata della chiamata in secondi: questa è durata 8 secondi. La lettera indica il tipo della chiamata (uscente, entrante, interna…): ‘M’ significa uscente, pertanto deve essere tariffata. Ignoriamo le altre cifre fino al numero telefonico chiamato, che inizia -in questo caso- con 0521… ed il numero interno che ha effettuato la chiamata, 47xx. Ignoriamo gli altri numeri, che nel mio contesto non servono.

Ho tutto quello che mi serve per calcolare il costo della telefonata, ad eccezione della “tipologia” di numero chiamato: sarà una urbana ? Oppure una interurbana ? Per rispondere a questa domanda, in maniera veloce e precisa, entra in gioco la nostra bella libreria libphonenumber in PHP.

Mi piace essere pragmatico, pertanto passo subito al pezzo di codice che elabora ogni singola riga del CDR per sputarmi fuori sia il prezzo che il tipo della telefonata:

/* Array numeri a tariffazione speciale: Numero - Prezzo al minuto */
$specialNumbers = array(
    '199100400' => array(0.15,'SKY Assistenza Clienti'), // SKY - Assistenza clienti
    '848800444' => array(0.0066,'Agenzia delle Entrate'), // Agenzia delle entrate - Tariffa URBANA su tutto il territorio nazionale
    '199303040' => array(0.1425,'Moby Lines Assistenza Clienti'), // Moby Lines - Assistenza clienti
    '848390166' => array(0.143,'powowowow Conferenze Telefoniche'), // powowowow - Conferenze telefoniche
    '840003003' => array(0.0,'Azienda Ospedaliera di Firenze CUP'), // Azienda Ospedaliera di Firenze - CUP
    '199803868' => array(0.1007,'TNT Servizio Clienti'), // TNT Servizio Clienti
    '199112088' => array(0.0,'Xerox Servizio Clienti'), // Xerox Servizio Clienti
    '199111999' => array(0.12,'Focus.it Info telefoniche') // Focus.it Info telefoniche
// ...
);

class cdrRow {
    var $calldate;
    var $src;
    var $dst;
    var $duration;
    var $context;
    var $sense;
    var $type;
    var $price;
    var $error=false;
    var $note;

    function calculatePrice($prefix) {
	// Come argomento, richiede il prefisso della localita
        // Prima di chiamare questa funzione assicurarsi di aver popolato le variabili !
        // RETURN: calcola il prezzo ed il tipo della chiamata, compila il campo note con il geocoding

        $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance();

	$geocoder = \libphonenumber\geocoding\PhoneNumberOfflineGeocoder::getInstance();

        try {
    	    $numberProto = $phoneUtil->parseAndKeepRawInput($this->dst, "IT");

	    if($phoneUtil->isValidNumber($numberProto)) {
	        $regionCode = $phoneUtil->getRegionCodeForNumber($numberProto);

		switch($phoneUtil->getNumberType($numberProto)) {
		    case 0: // Chiamata linea fissa 
		        if($regionCode != 'IT') {
		    	    $this->type = 'I';
				// Completare con tariffazione per AREA
			    $this->price = (double)(0.29/60.0)*$this->duration;
			} else {
			    $tmpNumber = $phoneUtil->getNationalSignificantNumber($numberProto);
			    if(substr_compare($tmpNumber,$prefix,0,4) == 0) { // Distrettuale
	    		        $this->price = (double)(0.0066/60.0)*$this->duration;
	    		        $this->type = 'U';
			    } else {
	    		        $this->price = (double)(0.0086/60.0)*$this->duration;
	    		        $this->type = 'E';
	    		    }
			}
			break;
		    case 1:
			$this->type = 'M';
			$this->price = (double)(0.05/60.0)*$this->duration;
			break;
		    case 3:
		        $this->type = 'V';
			$this->price = 0;
			break;
		    default:
			global $specialNumbers;
			// Prova a identificare il numero direttamente
			$tmpNumber = $phoneUtil->getNationalSignificantNumber($numberProto);

			$this->error = $tmpNumber;

			if(in_array($tmpNumber,array_keys($specialNumbers))) {
			    $this->type = 'S';
			    $this->price = (double)($specialNumbers[$tmpNumber][0]/60.0)*$this->duration;
			    $this->note = $specialNumbers[$tmpNumber][1];
			} else {
			    $this->type = '?';
			    $this->price = 0;
			}
			break;
		}
		$this->note = $geocoder->getDescriptionForNumber($numberProto, "it_IT");
		return true;
	    } else {
		$this->error = "Invalid number";
		return false;
	    }
	} catch (Exception $e) {
	    $this->error = $e;
	    return false;
	}
    }
}

Ovviamente questo pezzo di codice va oltre il semplice scopo di identificare il tipo della chiamata ed è inserito in un contesto ad-hoc: proviamo a spiegarlo passo passo, per farvi capire le potenzialità della libreria.

Per prima cosa, prendendo spunto dagli esempi della libreria, mando in pasto il mio numero ‘05219xxxxx’ (funzione parseAndKeepRawInput) indicando che il mio contesto di riferimento è ‘IT’.

Una semplice funzione booleana mi conferma che il numero sia valido (isValidNumber) restituendomi ‘true’, così proseguo nell’analisi discriminando la tipologia della chiamata con getNumberType che restituisce:

  • 0 per le chiamate a linee fisse
  • 1 per le chiamate verso numerazioni mobili (“cellulari”)
  • 3 per i numeri verdi

Iniziamo con le chiamate verso linee fisse, le più complesse nell’elaborazione. Per prima cosa identifichiamo le eventuali chiamate internazionali, grazie alla funzione getRegionCodeForNumber che mi restituisce l’identificativo del paese chiamato (IT se Italia). 

Nota bene: in questa funzione manca la discriminante tariffaria per le diverse Aree internazionali.

Se è una chiamata nazionale (‘IT’), devo verificare se si tratta di urbana (semplice operazione di match del prefisso) o di extraurbana e tariffarle di conseguenza.

Le chiamate verso numeri mobili sono più semplici, in quanto è sufficiente moltiplicare la durata per il costo al minuto. Quelle verso i numeri verdi, poi, il costo è sempre zero.

Una parolina devo spenderla per le numerazioni a tariffazione speciale 199/848 etc etc. Non essendoci un listino predeterminato, è necessario fare una ricognizione numero per numero (ove possibile, se ha senso) ed attuare una strategia come quella di un hash table dove la chiave è il numero ed il valore può essere la tariffazione al minuto (mi sono spinto più in là, aggiungendo anche la descrizione della chiamata). Se proprio non riesco ad identificare il numero, segno con un ‘?’ per una elaborazione successiva o manuale.

Interessante anche la funzione geocoding (getDescriptionForNumber), che riesce ad identificare la località del numero per una analisi ancora più approfondita del traffico.

Il risultato di tutto questo, per il momento, potete vederla nella immagine “in evidenza”. Il tutto grazie alla free software community.

(Visitato in totale 7 volte, oggi 1 visite)

Rispondi

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.