With the possibility of a new NIMS project, FIMS (Future Information Management System), we have the opportunity to create something better by learning from the issues that have affected us in the past with NIMS.
Objective: To create an online facility inventory for Ethiopia
Goals:
- Software Maintainability: "do one thing and do it well"
- Offline & mobile data collection
- Updated data should show up immediately on the site
- Efficient indicator calculations
- Enable easier data analysis (than flat file based NIMS)
+----+ +-------+
| | | |
| | | |
| | | |
| xx | +---++--+
| xx | xxx
+----+ xxxxxxxxx
iPhone/Android Computer
^ ^
| |
+--+--------+
|
|
v
+------------------+ +------------------+
| FIMS | +-------------+ | Backend Service |
|------------------| | Database | |------------------|
| | |-------------| | |
| Web App & | | | | Updates computed |
| | <-------> +-------------+ <------> | |
| JSON API | | | | indicators |
| | |-------------| | |
| | | | | |
+------------------+ +-------------+ +------------------+
Since 1995, Ethiopia has been divided in three levels. From highest to lowest level they are:
- Regions: 11 regions + 2 chartered cities
- Zones: more than 68 zones in the country. # of zones per region varies widely.
- Districts (aka Woreda): 670 rural woreda + about 100 urban woreda
Facility data will be entered and updated through the website. Offline data collection on iOS/Android devices will be supported through the use of HTML5 APIs.
Assuming we have a phone running Android 3.0 or higher Released 2011, the following HTML5 features should be present:
- Offline ability
- Local storage
- GPS capture
- Taking facility photos
Previously we've had problems with duplicate facility data collected at different times. This was tricky since we relied on many different ways to generate facility IDs.
Here is one possible solution:
- Automatically generate an integer ID when adding a new facility.
- Downloadable facility data for a certain region to allow offline data updating.
- Have the ability to merge facilities which have similar GPS coordinates and names.
Indicators are attributes of a Facility, District, Zone, or Region. Indicators have an id such as num_children
, as well as a descriptive name such as Number of Children
.
There are two types of indicators. Normal indicators, which are generated through user input, and Computed indicators which are generated through a calculation.
In order to discourage overly complex calculations as in the past, the following limitations will be put in place:
-
Facility level
- Facilities have normal and computed indicators.
- Computed indicators can only be generated from normal indicators from the same facility.
-
District level
- Districts have normal and computed indicators.
- Computed indicators can make use of normal indicators from the District level.
- Computed indicators can also make use of any indicator from the Facilities it contains.
-
Zone level
- Zones have normal and computed indicators.
- Computed indicators can make use of normal indicators from the Zone level.
- Computed indicators can also make use any indicator from the Districts it contains.
-
Regional level
- Has normal and computed indicators.
- Computed indicators can make use of normal indicators from the same level.
- Computed indicators can also make use of any indicator from the level below.
-
National level
- Same as the Regional level (you get the picture)...
Note: The intention is to make dependencies more obvious for the majority of computed indicators. Although these limitations could be easily be sidestepped, the programmer or data scientist will be more aware of when a particular calculation is computationally expensive.
By using a decorator to register each computed indicator, we can keep track of its dependencies, as well as its metadata.
class Facility(Model):
district = RelationField('District')
num_boys = IntegerField(name='Number of Boys')
num_girls = IntegerField(name='Number of Girls')
@indicator(name='Number of Children')
def num_children(self):
return self.num_boys + self.num_girls
class District(Model):
zone = RelationField('Zone')
@indicator(name='Total number of children in the district', depends=['Facility.num_children'])
def num_children(self, facilities):
total = 0
for facility in facilities:
total += facility.num_children
return total
class Zone(Model):
region = RelationField('Region')
@indicator(name='Total number of children in a zone', depends=['District.num_children'])
def num_children(self, districts):
total = 0
for district in districts:
total += district.num_children
return total
...
Here is a simplified example of the updates would be be done after updating the normal indicator num_boys
in a facility:
Facility.num_children
: 1 computation, 1 database query (1 Facility)District.num_children
: 1 computation, 1 database query (~1000 Facilities in a District)Zone.num_children
: 1 computation, 1 database query (~100 Districts in a Zone)Region.num_children
: 1 computation, 1 database query (~25 Zones in a Region)Country.num_children
: 1 computation, 1 database query (13 Regions in Ethiopia)
In this example, the 5 updates required after updating num_boys
would be pushed to a task queue which would later be executed by a background task. In this way, users will not have to wait for updates to occur.
class Facility(Model):
district = RelationField('District')
num_teachers = IntegerField(name='Number of Teachers')
num_students = IntegerField(name='Number of Students')
@indicator
def student_teacher_ratio(self):
if num_students and num_teachers:
return num_students / num_teachers
class District(Model):
zone = RelationField('Zone')
@indicator(depends=['Facility.num_teachers'])
def num_teachers(self, facilities):
return sum_attr(facilities, 'num_teachers')
@indicator(depends=['Facility.num_teachers', 'Facility.num_students'])
def student_teacher_ratio(self, facilities):
facilities = drop_missing(facilities, 'num_teachers', 'num_students')
self._num_students_for_ratio = sum_attr(facilities, 'num_teachers')
self._num_teachers_for_ratio = sum_attr(facilities, 'num_students')
return self._num_students_for_ratio / self._num_teachers_for_ratio
class Zone(Model):
region = RelationField('Region')
@indicator(depends=['District.student_teacher_ratio'])
def student_teacher_ratio(self, districts):
return sum_attr(districts, '_num_students_for_ratio') / sum_attr(districts, '_num_teachers_for_ratio')