Portia

A small service that allows one to:

  • lookup phone numbers in number porting databases.
  • annotate records with extra information when needed.
_images/portia.gif

Installation

First make sure you have Redis installed and running:

$ apt-get install redis-server
$ virtualenv ve
$ source ve/bin/activate
(ve)$ pip install portia
(ve)$ portia --help

Importing database files

(ve)$ portia import porting-db path/to/file.csv

Running the web server

(ve)$ portia run
2015-10-16 18:22:35+0200 [-] Log opened.
2015-10-16 18:22:35+0200 [-] Starting factory <txredisapi.RedisFactory instance at 0x105b3bea8>
2015-10-16 18:22:35+0200 [HiredisProtocol,client] Site starting on 8000
2015-10-16 18:22:35+0200 [-] Starting factory <twisted.web.server.Site instance at 0x105b6ef80>

By default this will listen on localhost:8000.

Resolving

Resolving an MNO can be done via the /resolve endpoint:

$ curl localhost:8000/resolve/27761234567
{
   "entry": {
     "ported-to-timestamp": "2015-10-16T19:26:41.943293",
     "observed-network-timestamp": "2015-10-16T19:49:21.130930",
     "ported-to": "CELLC",
     "observed-network": "MTN"
   },
   "network": "MTN",
   "strategy": "observed-network"
 }

The network key has the most likely network the MSISDN is homed on. The strategy is the strategy used to make that decision. The strategy is currently very naïve, it gets the most recent observed-network or ported-to timestamp and returns that.

If all else fails it falls back to guessing based on the prefix:

$ curl localhost:8000/resolve/27760000000
{
  "entry": {},
  "network": "VODACOM",
  "strategy": "prefix-guess"
}

Querying

Looking up everything for a known phone number:

$ curl http://localhost:8000/entry/27123456780
{
  "ported-to-timestamp": "2015-10-11T00:00:00",
  "ported-from": "MNO1",
  "ported-to": "MNO2",
  "ported-from-timestamp": "2015-10-11T00:00:00"
}

Looking up a single key for a phone number:

$ curl http://localhost:8000/entry/27123456780/ported-to
{
  "ported-to": "MNO2",
  "ported-to-timestamp": "2015-10-11T00:00:00"
}

Annotating

Portia has a number of defined annotations that it supports, these are:

  • observed-network
  • ported-from
  • ported-to
  • do-not-call

Adding an observed network annotation for a phone number:

$ curl -XPUT -d MNO3 http://localhost:8000/entry/27123456780/observed-network
"MNO3"

$ curl http://localhost:8000/entry/27123456780/observed-network
{
  "ported-to": "MNO2",
  "ported-to-timestamp": "2015-10-11T00:00:00",
  "ported-from": "MNO1",
  "ported-from-timestamp": "2015-10-11T00:00:00",
  "observed-network": "MNO3",
  "observed-network-timestamp": "2015-10-13T06:54:18.797250"
}

Custom annotations are allowed if the key is prefixed with X-:

$ curl -XPUT -d bar http://localhost:8000/entry/27123456780/X-foo
"bar"

Running the TCP socket server

(ve)$ portia run --tcp
2015-10-16 18:22:51+0200 [-] Log opened.
2015-10-16 18:22:51+0200 [-] Starting factory <txredisapi.RedisFactory instance at 0x10a969ea8>
2015-10-16 18:22:51+0200 [HiredisProtocol,client] JsonProtocolFactory starting on 8001
2015-10-16 18:22:51+0200 [-] Starting factory <portia.protocol.JsonProtocolFactory instance at 0x10a99cf38>
2015-10-16 18:22:51+0200 [HiredisProtocol,client] Site starting on 8000
2015-10-16 18:22:51+0200 [-] Starting factory <twisted.web.server.Site instance at 0x10a9a03b0>

By default this will listen on localhost:8001. You can specify a different endpoint with --tcp-endpoint=tcp:8080:interface=127.0.0.1 as an example

JSON is used for the socket protocol. It uses \r\n as a delimiter

Note

The timestamp values are all in ISO 8601 format. Timezone naive timestamps are assumed to be in UTC and will be stored internally as such.

Get

$ telnet localhost 8001
> {"cmd": "get", "id": 1, "version": "0.1.0", "request": {"msisdn": "27761234567"}}
< {"status": "ok", "cmd": "reply", "version": "0.1.0", "reference_id": 1, "response": {"ported-to-timestamp": "2015-10-16T19:26:41.943293", "ported-to": "CELLC", "X-Foo-timestamp": "2015-10-19T18:37:36.294939", "observed-network": "MTN", "observed-network-timestamp": "2015-10-16T19:49:21.130930"}, "reference_cmd": "get"}

Annotate

$ telnet localhost 8001
> {"cmd": "annotate", "id": 2, "version": "0.1.0", "request": {"msisdn": "27761234567", "key": "X-Foo", "value": "bar"}}
< {"status": "ok", "cmd": "reply", "version": "0.1.0", "reference_id": 2, "response": "OK", "reference_cmd": "annotate"}

$ telnet localhost 8001
> {"cmd": "get", "id": 3, "version": "0.1.0", "request": {"msisdn": "27761234567"}}
< {"status": "ok", "cmd": "reply", "version": "0.1.0", "reference_id": 3, "response": {"ported-to-timestamp": "2015-10-16T19:26:41.943293", "ported-to": "CELLC", "X-Foo-timestamp": "2015-10-19T18:44:33.710381", "observed-network": "MTN", "X-Foo": "bar", "observed-network-timestamp": "2015-10-16T19:49:21.130930"}, "reference_cmd": "get"}

Resolve

$ telnet localhost 8001
> {"cmd": "resolve", "id": 4, "version": "0.1.0", "request": {"msisdn": "27761234567"}}
< {"status": "ok", "cmd": "reply", "version": "0.1.0", "reference_id": 4, "response": {"entry": {"ported-to-timestamp": "2015-10-16T19:26:41.943293", "ported-to": "CELLC", "X-Foo-timestamp": "2015-10-19T18:44:33.710381", "observed-network": "MTN", "X-Foo": "bar", "observed-network-timestamp": "2015-10-16T19:49:21.130930"}, "network": "MTN", "strategy": "observed-network"}, "reference_cmd": "resolve"}