SMS

with RapidSMS

ChildCount is built using the RapidSMS framework. The following few sections will introduce you to a few key RapidSMS concepts: the router, backends, and applications.

Router

The [RapidSMS] router is the part of ChildCount+ that handles incoming and outgoing messages, and it operates independently from whatever Web server you are using to view the ChildCount+ dashboard. (Other parts of ChildCount+ serve Web pages and generate analytical reports.) If you are deploying ChildCount+ yourself, you will probably want to learn how to use the router to respond to special SMS keywords or to collect deployment-specific data. See the SMS section of Understanding the Components for an overview of the router.

Backends

The RapidSMS router interacts with the outside world via a set of “backends” (whoever chose this terminology must have had a sense of humor...).

An application that uses SMS, Web data entry, and email to interact with the world would have three backends: one for each of these three transport mechanisms. Backends inherit from rapidsms.backends.backend.Backend and they use a common interface to tell the RapidSMS router how to send and receive messages. The active backends are specified in the local.ini file in the root ChildCount+ directory.

Applications

The router treats incoming messages the same way no matter where they come from (by SMS, email, etc) – every message gets parsed into a rapidsms.message.Message object and handed to the active applications.

As described in Understanding the Components, RapidSMS steps through the active applications listed in the local.ini file and calls [app_name].App.handle() on each, with the rapidsms.message.Message object as an argument. Each application processes the message and returns False if the message should be passed on to the rest of the active applications, and True otherwise.

Here is an example App.handle() definition that responds to a message FLIP with the message Heads or Tails:

class App(rapidsms.app.App):
    def handle(self, message):
        if message.text.strip().upper() == "FLIP":
            response = random.choice(["Heads", "Tails"])
            message.respond(response)
            return True
        else:
            return False

As in Django, you can have many RapidSMS apps running on the same ChildCount+ server. The order in which the apps get to handle messages is determined by the order in which they appear in the local.ini file.

Some useful SMS-related apps are:

  • /apps/childcount – Handles all ChildCount+ messages
  • /apps/fortune – Responds to the message FORTUNE with a Ugandan proverb
  • /apps/logger_ng – Stores all messages in a message log database table
  • /apps/ping – When it receives a message PING, it responds PONG

with ChildCount+

The body of the ChildCount+ message processing happens in /apps/childcount/app.py – ChildCount’s RapidSMS application. The following sections describe how components within the ChildCount+ application process messages and how you can customize these components.

SMS Forms and Commands

To understand ChildCount+ SMS processing, you must know the difference between a form, a command, and a report.

Caution

We have recklessly overloaded the term “form.” The word “form” can refer to the paper paper forms filled out by CHWs (see: Forms) or it can refer to SMS forms – the logic that parses and processes messages (described below).

Forms and commands are both means of connecting SMS keywords to bits of application processing logic. The difference is that SMS forms are part of a message that begins with a patient health identifier (health ID) and (commands) are consist of messages that begin with a keyword.

Examples of SMS Commands
Message Sent to Server Action Taken by Server
CHECKID abc123 Reply to sender with a message explaining whether or not the health ID abc123 is valid.
LOOKUP joe Reply to sender with a message listing all of the patients with name joe.
CANCEL Cancel the effect of the sender’s previous message.

As you see, all of the commands listed in the table begin with a keyword (like CHECKID). Commands are useful for situations where the message does not directly relate to a registered patient. Commands inherit from the class childcount.commands.CCCommand.

Other commands are listed in the commands API documentation (childcount.commands) and in the ChildCount+ source code in the folder /apps/childcount/commands.

Messagings containing SMS forms begin with a valid ChildCount+ health ID (see Health IDs), followed by a series of +CODE sequences. ChildCount+ checks the validity of the health ID before any of the form processing logic begins.

The SMS forms generally correspond to fields on the paper ChildCount+ forms. For example, the +V form below corresponds to the +V section of the ChildCount+ household visit form (paper form B). You can look at the paper forms here: Forms.

Message Sent to Server Action Taken by Server
ABC123 +V Y 2 BN FP Record that the CHW who sent the message conducted a household visit at the household headed by the person whose health ID is ABC123. The arguments to the +V form indicate that there was a household member present (Y), that there were two under-fives present (2), and that the CHW discussed bednets and family planning (BN FP) at the household visit.
56HG2 +F Y +S FV VM +R B Record that the patient with health ID 56HG2 tested positive with a rapid diagnostic test for malaria (+F Y), that the patient has fever and is vomiting (+S FV VM), and that the CHW made a 24-hour referral for this patient to a health center (+R B).

Note that it is possible (and encouraged) to send many forms relating to the same patient within the same message. Combining forms this way cuts down on the number of SMS messages that CHWs need to send per household visit.

SMS forms reside in the directory apps/childcount/forms and the API documentation is here: childcount.forms. SMS forms inherit from childcount.forms.CCForm.CCForm.

You enable commands and forms by including them in the list of active commands/forms in the local.ini configuration file.

SMS Reports

Caution

We have shamelessly overloaded the term “report:” The word “report” can refer to the printed paper reports generated by ChildCount+ (see Printed Reports) or it can refer to Report models (described below).

Reports (in the context of messaging) are Django models for storing information collected from a ChildCount+ SMS form. In general, the form holds parsing and validation logic for the collected data, while the report is where the data ends up being stored. A “report” in this context is a Django model that corresponds to a database table holding the form data.

For example, the +V SMS form collects data about household visits. There is a class childcount.forms.HouseholdVisitForm that defines the parsing and validation logic for the +V form. Once the data has been parsed from the +V form and validated, it is stored using the Django ORM as a childcount.reports.HouseholdVisitReport object.

All of the ChildCount+ reports are located in apps/childcount/models/reports.py, and they inherit from childcount.models.CCReport.

Defining a Command

Say you want to define a new command called ReverseTextCommand that users invoke by SMS like this:

REVERSE First Second Third

To define this command, you must:

  1. Look through the existing commands in apps/childcount/commands to make sure that the command you want does not already exist. There are lots of useful commands defined there, so please check first.

  2. Create a file apps/childcount/commands/ReverseTextCommand.py

  3. Within this new file, import childcount.commands.CCCommand and define a new class that inherits from it:

    from childcount.commands import CCCommand
    from childcount.utils import authenticated
    
    class ReverseTextCommand(CCCommand):
    
        KEYWORDS = {
            'en': ['reverse'],
            'fr': ['inverse'],
        }
    
        @authenticated
        def process(self):
            ...do actual work here
    

    See childcount.commands.CCCommand for the definition of the childcount.command.CCCommand class.

  4. In apps/childcount/commands/__init__.py, add the line:

    from childcount.commands.ReverseTextCommand import ReverseTextCommand
    
  5. In your local.ini file in the root ChildCount+ directory, add ReverseTextCommand to the list of active commands:

    ...
    [childcount]
    commands = WhoCommand, LookupCommands, ReverseTextCommand, ...
    ...
    

Adding a New Form

Say you want to define a new form called DogsForm that will record the number of dogs a person has in their household. Users will invoke the SMS form like this:

HEALTH_ID +DOGS 2

...where HEALTH_ID is replaced by the person’s ChildCount+ health identifier and 2 is replaced by the number of dogs that person has in their household.

To define this new form, you must:

  1. Look through the existing forms in apps/childcount/forms to make sure that the form you want does not already exist. There are lots of useful forms defined there, so please check first.

  2. If you want to store the form data in the database (and you probably do), then you will need to create a Django model that represents your report data. Since DogForm only takes one parameter – an integer number of dogs, this will be straightforward. You need to create a new model that inherits from the childcount.reports.CCReport abstract model.

    To do this, edit the file apps/childcount/models/reports.py. At the end of the file, add the code:

    class DogReport(CCReport):
        class Meta:
            app_label = 'childcount'
            db_table = 'cc_dogreport'
            verbose_name = _("Dog Report")
            verbose_name_plural = _("Dog Reports")
    
        dog_count = models.PositiveIntegerField(_("Number of dogs"))
    
    reversion.register(DogReport, follow=['ccreport_ptr'])
    

    This defines a new model (i.e., database table) that will store your dog data. This is just standard Django model stuff, so you can consult the Django Documentation for details on how it all works. The only trickiness is that we use django-polymorphic and django-reversion to add some extra features to the models.

    Django-polymorphic allows all models that inherit from childcount.models.reports.CCReport` to share common database columns. All reports have an associated childcount.models.Encounter and django-polymorphic allows us to declare this relationship only once (in childcount.models.reports.CCReport) and all other models get those fields too.

    Django-reversion allows some version control on database tables. We use this to implement the CANCEL command (childcount.commands.CancelCommand.CancelCommand), which performs an “undo” operation for the previously sent SMS. Django-reversion has high overhead and does not always work properly so we may remove it in the near future.

  3. Use South to create a new database migration for this report model. From the command line run:

    # Change to your CC+ directory
    cd ~/sms
    ./rapidsms schemamigration childcount --auto
    

    South should detect the new model and create a migration for it.

  4. Create the database table. From your command line, run:

    # Change to your CC+ directory
    cd ~/sms
    ./rapidsms migrate childcount
    
  5. Now that the database table for storing your data has been created, you have to define the parsing logic in a childcount.forms.CCForm.CCForm object. To do this, create a file apps/childcount/forms/DogsForm.py

  6. Within this new file, import childcount.forms.CCForm.CCForm and define a new class that inherits from it:

    from childcount.forms import CCForm
    from childcount.models import Encounter
    from childcount.utils import authenticated
    
    class DogsForm(CCForm):
    
        KEYWORDS = {
            'en': ['dogs'],
            'fr': ['chiens'],
        }
    
        ENCOUNTER_TYPE = Encounter.TYPE_HOUSEHOLD
    
        @authenticated
        def process(self, patient):
            ...do actual work here
    

    See childcount.forms.CCForm for the definition of the childcount.forms.CCForm.CCForm class.

  7. In apps/childcount/forms/__init__.py, add the line:

    from childcount.forms.DogForm import DogForm
    
  8. In your local.ini file in the root ChildCount+ directory, add DogForm to the list of active commands:

    ...
    [childcount]
    forms = PatientRegistrationForm, BirthForm, DogForm, ...
    ...