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.
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:
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.Create a file
apps/childcount/commands/ReverseTextCommand.py
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.In
apps/childcount/commands/__init__.py
, add the line:from childcount.commands.ReverseTextCommand import ReverseTextCommand
In your
local.ini
file in the root ChildCount+ directory, addReverseTextCommand
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:
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.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 thechildcount.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 associatedchildcount.models.Encounter
and django-polymorphic allows us to declare this relationship only once (inchildcount.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.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.
Create the database table. From your command line, run:
# Change to your CC+ directory cd ~/sms ./rapidsms migrate childcount
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 fileapps/childcount/forms/DogsForm.py
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.In
apps/childcount/forms/__init__.py
, add the line:from childcount.forms.DogForm import DogForm
In your
local.ini
file in the root ChildCount+ directory, addDogForm
to the list of active commands:... [childcount] forms = PatientRegistrationForm, BirthForm, DogForm, ... ...