예제 #1
0
    def test_project_report(self):
        prj = Project.Create(self.u)
        prj.Update(title="New Project",
                   subhead="Project subhead",
                   due=datetime(2017, 4, 5))
        prj.set_progress(3)
        prj.put()

        self._test_report(
            {'type': REPORT.PROJECT_REPORT},
            [[
                "Date Created", "Date Due", "Date Completed", "Date Archived",
                "Title", "Subhead", "Links", "Starred", "Archived", "Progress",
                'Progress 10%', 'Progress 20%', 'Progress 30%', 'Progress 40%',
                'Progress 50%', 'Progress 60%', 'Progress 70%', 'Progress 80%',
                'Progress 90%', 'Progress 100%'
            ],
             [
                 tools.sdatetime(prj.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 tools.sdatetime(prj.dt_due, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 tools.sdatetime(prj.dt_completed, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 tools.sdatetime(prj.dt_archived,
                                 fmt="%Y-%m-%d %H:%M:%S %Z"), "New Project",
                 "Project subhead", "", "0", "0", "30%", "N/A", "N/A",
                 tools.sdatetime(tools.dt_from_ts(prj.progress_ts[2]),
                                 fmt="%Y-%m-%d %H:%M:%S %Z"), "N/A", "N/A",
                 "N/A", "N/A", "N/A", "N/A", "N/A"
             ]])
예제 #2
0
 def entityData(self, task):
     row = [
         tools.sdatetime(task.dt_created, fmt=DATE_FMT),
         tools.sdatetime(task.dt_due, fmt=DATE_FMT),
         tools.sdatetime(task.dt_done, fmt=DATE_FMT), task.title,
         "1" if task.is_done() else "0", "1" if task.archived else "0"
     ]
     return row
예제 #3
0
 def entityData(self, hd):
     habit = hd.habit.get()
     row = [
         tools.sdatetime(hd.dt_created, fmt=DATE_FMT),
         tools.sdatetime(hd.dt_updated, fmt=DATE_FMT),
         tools.iso_date(hd.date), habit.name if habit else "",
         "1" if hd.done else "0", "1" if hd.committed else "0"
     ]
     return row
예제 #4
0
 def entityData(self, analysis):
     sensor_name = analysis.sensor.name if analysis.sensor else ""
     row = [
         analysis.key().name(), sensor_name,
         tools.sdatetime(analysis.dt_created),
         tools.sdatetime(analysis.dt_updated)
     ]
     for col in self.columns:
         value = analysis.columnValue(col, default="")
         row.append(value)
     return row
예제 #5
0
 def entityData(self, hd):
     habit = hd.habit.get()
     row = [
         tools.sdatetime(hd.dt_created, fmt=DATE_FMT),
         tools.sdatetime(hd.dt_updated, fmt=DATE_FMT),
         tools.iso_date(hd.date),
         habit.name if habit else "",
         "1" if hd.done else "0",
         "1" if hd.committed else "0"
     ]
     return row
예제 #6
0
 def entityData(self, task):
     timer_ms = task.timer_total_ms or 0
     sess = task.timer_complete_sess or 0
     row = [
         tools.sdatetime(task.dt_created, fmt=DATE_FMT),
         tools.sdatetime(task.dt_due, fmt=DATE_FMT),
         tools.sdatetime(task.dt_done, fmt=DATE_FMT), task.title,
         "1" if task.is_done() else "0", "1" if task.archived else "0",
         str(timer_ms / 1000),
         str(sess)
     ]
     return row
예제 #7
0
 def entityData(self, analysis):
     sensor_name = self.sensor_lookup.get(
         tools.getKey(Analysis, 'sensor', analysis, asID=False), "")
     row = [
         analysis.key().name(), sensor_name,
         tools.sdatetime(analysis.dt_created),
         tools.sdatetime(analysis.dt_updated)
     ]
     for col in self.columns:
         value = analysis.columnValue(col, default="")
         row.append(value)
     return row
예제 #8
0
    def test_habit_report(self):
        habit_run = Habit.Create(self.u)
        habit_run.Update(name="Run")
        habit_run.put()
        marked_done, hd = HabitDay.Toggle(habit_run, datetime.today())

        self._test_report(
            {'type': REPORT.HABIT_REPORT},
            [["Created", "Updated", "Date", "Habit", "Done", "Committed"],
             [
                 tools.sdatetime(hd.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 tools.sdatetime(hd.dt_updated, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 tools.iso_date(datetime.now()), "Run", "1", "0"
             ]])
예제 #9
0
 def entityData(self, task):
     timer_ms = task.timer_total_ms or 0
     sess = task.timer_complete_sess or 0
     row = [
         tools.sdatetime(task.dt_created, fmt=DATE_FMT),
         tools.sdatetime(task.dt_due, fmt=DATE_FMT),
         tools.sdatetime(task.dt_done, fmt=DATE_FMT),
         task.title,
         "1" if task.is_done() else "0",
         "1" if task.archived else "0",
         str(timer_ms / 1000),
         str(sess)
     ]
     return row
예제 #10
0
 def entityData(self, analysis):
     sensor_name = self.sensor_lookup.get(tools.getKey(Analysis, 'sensor', analysis, asID=False), "")
     row = [analysis.key().name(), sensor_name, tools.sdatetime(analysis.dt_created), tools.sdatetime(analysis.dt_updated)]
     for col in self.columns:
         value = analysis.columnValue(col, default="")
         row.append(value)
     return row
예제 #11
0
    def test_goal_report(self):
        g = Goal.Create(self.u, "2017")
        g.Update(text=["Goal 1", "Goal 2"], assessments=[3, 4])
        g.put()

        self._test_report(
            {'type': REPORT.GOAL_REPORT},
            [

                [
                    'Goal Period',
                    'Date Created',
                    'Text 1',
                    'Text 2',
                    'Text 3',
                    'Text 4',
                    'Goal Assessments',
                    'Overall Assessment'
                ],
                [
                    "2017",
                    tools.sdatetime(g.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    "Goal 1",
                    "Goal 2",
                    "",
                    "",
                    "\"3,4\"",
                    "3.5"
                ]
            ]
        )
예제 #12
0
    def test_task_report(self):
        due_date = datetime(2017, 10, 2, 12, 0)
        task = Task.Create(self.u, "New task", due=due_date)
        task.put()

        self._test_report(
            {'type': REPORT.TASK_REPORT},
            [
                [
                    'Date Created',
                    'Date Due',
                    'Date Done',
                    'Title',
                    'Done',
                    'Archived',
                    'Seconds Logged',
                    'Complete Sessions Logged'
                ],
                [
                    tools.sdatetime(task.dt_created, fmt=DATE_FMT),
                    "2017-10-02 12:00:00 UTC",
                    "N/A",
                    "New task",
                    "0",
                    "0",
                    "0",
                    "0"
                ]
            ]
        )
예제 #13
0
 def entityData(self, prj):
     row = [
         tools.sdatetime(prj.dt_created, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_due, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_completed, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_archived, fmt=DATE_FMT), prj.title,
         prj.subhead, ', '.join(prj.urls), "1" if prj.starred else "0",
         "1" if prj.archived else "0",
         "%d%%" % (prj.progress * 10)
     ]
     for i in range(10):
         val = ""
         if prj.progress_ts and len(prj.progress_ts) > i:
             ms = prj.progress_ts[i]
             val = tools.sdatetime(tools.dt_from_ts(ms), fmt=DATE_FMT)
         row.append(val)
     return row
예제 #14
0
 def entityData(self, goal):
     n_texts = len(goal.text) if goal.text else 0
     row = [goal.key.id(), tools.sdatetime(goal.dt_created, fmt=DATE_FMT)]
     slots = [(goal.text[i] if n_texts > i else "") for i in range(self.n_slots)]
     row += slots
     row += [','.join([str(a) for a in goal.assessments]) if goal.assessments else ""]
     row += [str(goal.assessment) if goal.assessment else ""]
     return row
예제 #15
0
 def entityData(self, goal):
     n_texts = len(goal.text) if goal.text else 0
     row = [goal.key.id(), tools.sdatetime(goal.dt_created, fmt=DATE_FMT)]
     slots = [(goal.text[i] if n_texts > i else "") for i in range(self.n_slots)]
     row += slots
     row += [','.join([str(a) for a in goal.assessments]) if goal.assessments else ""]
     row += [str(goal.assessment) if goal.assessment else ""]
     return row
예제 #16
0
 def entityData(self, apilog):
     request_id = "ID:" + str(apilog.key().id())
     uid = "ID:%s" % tools.getKey(APILog, 'user', apilog, asID=True)
     row = [
         request_id, uid,
         tools.sdatetime(apilog.date), apilog.path, apilog.method,
         apilog.request
     ]
     return row
예제 #17
0
 def entityData(self, u):
     row = [
         "ID:%s" % u.key().id(),
         tools.sdatetime(u.dt_created), u.name, u.email if u.email else "",
         u.phone if u.phone else "",
         ', '.join([str(gid) for gid in u.group_ids]),
         u.custom_attrs if u.custom_attrs else ""
     ]
     return row
예제 #18
0
 def entityData(self, sensor):
     sensor_type_id = tools.getKey(Sensor, 'sensortype', sensor, asID=True)
     row = [
         "ID:%s" % sensor.key().name(), sensor.name,
         "ID:%s" % sensor_type_id,
         tools.sdatetime(sensor.dt_created),
         sensor.contacts if sensor.contacts else "",
         ', '.join([str(gid) for gid in sensor.group_ids])
     ]
     return row
예제 #19
0
 def entityData(self, rec):
     row = [
         "ID:%s" % rec.key().name(),
         "ID:%s" %
         tools.getKey(Record, 'sensor', rec, asID=False, asKeyName=True),
         tools.sdatetime(rec.dt_recorded, fmt="%Y-%m-%d %H:%M:%S %Z")
     ]
     for col in self.columns:
         row.append(str(rec.columnValue(col, default="")))
     return row
예제 #20
0
    def test_task_report(self):
        due_date = datetime(2017, 10, 2, 12, 0)
        task = Task.Create(self.u, "New task", due=due_date)
        task.put()

        self._test_report({'type': REPORT.TASK_REPORT}, [
            'Date Created,Date Due,Date Done,Title,Done,Archived', ",".join([
                tools.sdatetime(task.dt_created, fmt=DATE_FMT),
                "2017-10-02 12:00:00 UTC", "N/A", "New task", "0", "0"
            ])
        ])
예제 #21
0
    def test_habit_report(self):
        habit_run = Habit.Create(self.u)
        habit_run.Update(name="Run")
        habit_run.put()
        marked_done, hd = HabitDay.Toggle(habit_run, datetime.today())

        self._test_report(
            {'type': REPORT.HABIT_REPORT},
            [
                ["Created", "Updated", "Date", "Habit", "Done", "Committed"],
                [
                    tools.sdatetime(hd.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    tools.sdatetime(hd.dt_updated, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    tools.iso_date(datetime.now()),
                    "Run",
                    "1",
                    "0"
                ]
            ]
        )
예제 #22
0
 def entityData(self, goal):
     texts = len(goal.text) if goal.text else 0
     row = [
         tools.sdatetime(goal.dt_created,
                         fmt=DATE_FMT), goal.text[0] if texts > 0 else "",
         goal.text[1] if texts > 1 else "",
         goal.text[2] if texts > 2 else "",
         goal.text[3] if texts > 3 else "",
         str(goal.assessment) if goal.assessment else ""
     ]
     return row
예제 #23
0
 def entityData(self, prj):
     row = [
         tools.sdatetime(prj.dt_created, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_due, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_completed, fmt=DATE_FMT),
         tools.sdatetime(prj.dt_archived, fmt=DATE_FMT),
         prj.title,
         prj.subhead,
         ', '.join(prj.urls),
         "1" if prj.starred else "0",
         "1" if prj.archived else "0",
         "%d%%" % (prj.progress * 10)
     ]
     for i in range(10):
         val = ""
         if prj.progress_ts and len(prj.progress_ts) > i:
             ms = prj.progress_ts[i]
             val = tools.sdatetime(tools.dt_from_ts(ms), fmt=DATE_FMT)
         row.append(val)
     return row
예제 #24
0
 def entityData(self, alarm):
     alarm_id = alarm.key().id()
     sensor_id = tools.getKey(Alarm,
                              'sensor',
                              alarm,
                              asID=False,
                              asKeyName=True)
     sensor_name = self.sensor_lookup.get(sensor_id, "")
     rule_id = tools.getKey(Alarm, 'rule', alarm, asID=True)
     rule_name = str(
         self.rule_lookup.get(
             tools.getKey(Alarm, 'rule', alarm, asID=False), ""))
     apex = "%.2f" % alarm.apex if alarm.apex is not None else "--"
     row = [
         "ID:%s" % alarm_id,
         "ID:%s" % sensor_id, sensor_name,
         "ID:%s" % rule_id, rule_name, apex,
         tools.sdatetime(alarm.dt_start),
         tools.sdatetime(alarm.dt_end)
     ]
     return row
예제 #25
0
    def testDateTimePrinting(self):
        volley = [
            ( datetime(2015,1,1,12,0), "UTC", "2015-01-01 12:00 UTC" ),
            ( datetime(2015,1,1,12,25), "Africa/Nairobi", "2015-01-01 15:25 EAT" ),
            ( datetime(2015,1,25,4,21), None, "2015-01-25 04:21 UTC" ),
        ]

        for v in volley:
            dt = v[0]
            tz = v[1]
            target = v[2]
            result = tools.sdatetime(dt, tz=tz)
            self.assertEqual(result, target)
예제 #26
0
    def testDateTimePrinting(self):
        volley = [
            ( datetime(2015,1,1,12,0), "UTC", "2015-01-01 12:00 UTC" ),
            ( datetime(2015,1,1,12,25), "Africa/Nairobi", "2015-01-01 15:25 EAT" ),
            ( datetime(2015,1,25,4,21), None, "2015-01-25 04:21 UTC" ),
        ]

        for v in volley:
            dt = v[0]
            tz = v[1]
            target = v[2]
            result = tools.sdatetime(dt, tz=tz)
            self.assertEqual(result, target)
예제 #27
0
    def test_project_report(self):
        prj = Project.Create(self.u)
        prj.Update(title="New Project", subhead="Project subhead", due=datetime(2017, 4, 5))
        prj.set_progress(3)
        prj.put()

        self._test_report(
            {'type': REPORT.PROJECT_REPORT},
            [
                ["Date Created", "Date Due", "Date Completed", "Date Archived", "Title", "Subhead",
                 "Links", "Starred", "Archived", "Progress", 'Progress 10%', 'Progress 20%', 'Progress 30%',
                 'Progress 40%', 'Progress 50%', 'Progress 60%', 'Progress 70%', 'Progress 80%',
                 'Progress 90%', 'Progress 100%'],
                [
                    tools.sdatetime(prj.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    tools.sdatetime(prj.dt_due, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    tools.sdatetime(prj.dt_completed, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    tools.sdatetime(prj.dt_archived, fmt="%Y-%m-%d %H:%M:%S %Z"),
                    "New Project",
                    "Project subhead",
                    "",
                    "0",
                    "0",
                    "30%",
                    "N/A",
                    "N/A",
                    tools.sdatetime(tools.dt_from_ts(prj.progress_ts[2]), fmt="%Y-%m-%d %H:%M:%S %Z"),
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A"
                ]
            ]
        )
예제 #28
0
    def test_goal_report(self):
        g = Goal.Create(self.u, "2017")
        g.Update(text=["Goal 1", "Goal 2"], assessments=[3, 4])
        g.put()

        self._test_report(
            {'type': REPORT.GOAL_REPORT},
            [[
                'Goal Period', 'Date Created', 'Text 1', 'Text 2', 'Text 3',
                'Text 4', 'Goal Assessments', 'Overall Assessment'
            ],
             [
                 "2017",
                 tools.sdatetime(g.dt_created, fmt="%Y-%m-%d %H:%M:%S %Z"),
                 "Goal 1", "Goal 2", "", "", "\"3,4\"", "3.5"
             ]])
예제 #29
0
    def test_task_report(self):
        due_date = datetime(2017, 10, 2, 12, 0)
        task = Task.Create(self.u, "New task", due=due_date)
        task.put()

        self._test_report(
            {'type': REPORT.TASK_REPORT},
            [[
                'Date Created', 'Date Due', 'Date Done', 'Title', 'Done',
                'Archived', 'Seconds Logged', 'Complete Sessions Logged'
            ],
             [
                 tools.sdatetime(task.dt_created,
                                 fmt=DATE_FMT), "2017-10-02 12:00:00 UTC",
                 "N/A", "New task", "0", "0", "0", "0"
             ]])
예제 #30
0
    def testCeilingAlarmAndStandardProcessing(self):
        self.process = ProcessTask.Create(self.e)
        spec = json.dumps({ 'processers':[
            {
                'calculation': 'MAX({speed})',
                'column': 'max_speed',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            },
            {
                'calculation': '. + SUM({bearing})',
                'column': 'total_bearing',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            },
            {
                'calculation': '. + COUNT({bearing})',
                'column': 'count_bearing',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            },
            {
                'calculation': '. + COUNT(ALARMS())',
                'column': 'count_alarms',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            }
        ]})
        self.process.Update(spec=spec, rule_ids=[self.speeding_alarm.key().id()])
        self.process.put()

        # Apply our process to our sensor
        self.sp = SensorProcessTask.Create(self.e, self.process, self.vehicle_1)
        self.sp.put()

        BATCH_1 = {
            'speed': [0,5,15,35,60,80,83,88,85,78,75,75,76,81,89,92,90,83,78], # We speed twice
            'bearing': [0,0,0,0,5,3,3,3,4,5,0,0,0,0,1,1,2,3,2]
        }
        self.__createNewRecords(BATCH_1, first_dt=datetime.now() - timedelta(minutes=5))
        self.__runProcessing()

        # Confirm analyzed max speed
        a = Analysis.GetOrCreate(self.vehicle_1, ANALYSIS_KEY_PATTERN)
        self.assertIsNotNone(a)
        self.assertEqual(a.columnValue('max_speed'), max(BATCH_1['speed']))

        # Confirm we counted new alarms in analysis
        # self.assertEqual(a.columnValue('count_alarms'), 2) TODO: This fails!
        self.sp = SensorProcessTask.Get(self.process, self.vehicle_1)
        self.assertEqual(self.sp.status_last_run, PROCESS.OK)

        # Confirm speeding alarms (2)
        alarms = Alarm.Fetch(self.vehicle_1, self.speeding_alarm)
        self.assertEqual(len(alarms), 2)

        # Test alarm notifications
        # TODO: Test output of notification (e.g. log messages or contact records)
        a = alarms[0] # second alarm
        message = a.render_alert_message(recipient=self.owner)
        SPEEDING_ALERT_MESSAGE_RENDERED = "Hello Dan Owner, %s was speeding at 81 at %s" % (TEST_SENSOR_ID, tools.sdatetime(a.dt_start, fmt="%H:%M", tz="Africa/Nairobi"))
        self.assertEqual(message, SPEEDING_ALERT_MESSAGE_RENDERED)

        BATCH_2 = {
            'speed': [76,75,78,73,60],
            'bearing': [0,0,2,0,5]
        }
        self.__createNewRecords(BATCH_2)
        self.__runProcessing()

        a = Analysis.GetOrCreate(self.vehicle_1, ANALYSIS_KEY_PATTERN)
        self.assertEqual(a.columnValue('total_bearing'), sum(BATCH_1['bearing']) + sum(BATCH_2['bearing']))
        self.assertEqual(a.columnValue('count_bearing'), len(BATCH_1['bearing']) + len(BATCH_2['bearing']))
        self.assertEqual(a.columnValue('count_alarms'), 2)
        self.assertEqual(self.sp.status_last_run, PROCESS.OK)
예제 #31
0
 def entityData(self, alarm):
     sensor_name = str(self.sensor_lookup.get(tools.getKey(Alarm, 'sensor', alarm, asID=False), ""))
     rule_name = str(self.rule_lookup.get(tools.getKey(Alarm, 'rule', alarm, asID=False), ""))
     apex = "%.2f" % alarm.apex if alarm.apex is not None else "--"
     row = [sensor_name, rule_name, apex, tools.sdatetime(alarm.dt_start), tools.sdatetime(alarm.dt_end)]
     return row, []
예제 #32
0
 def entityData(self, rec):
     row = [tools.sdatetime(rec.dt_recorded, fmt="%Y-%m-%d %H:%M:%S %Z")]
     for col in self.columns:
         row.append(str(rec.columnValue(col, default="")))
     return row, []
예제 #33
0
 def entityData(self, alarm):
     sensor_name = self.sensor_lookup.get(tools.getKey(Alarm, 'sensor', alarm, asID=False), "")
     rule_name = str(self.rule_lookup.get(tools.getKey(Alarm, 'rule', alarm, asID=False), ""))
     apex = "%.2f" % alarm.apex if alarm.apex is not None else "--"
     row = [sensor_name, rule_name, apex, tools.sdatetime(alarm.dt_start), tools.sdatetime(alarm.dt_end)]
     return row
예제 #34
0
 def entityData(self, rec):
     row = [tools.sdatetime(rec.dt_recorded, fmt="%Y-%m-%d %H:%M:%S %Z")]
     for col in self.columns:
         row.append(str(rec.columnValue(col, default="")))
     return row
예제 #35
0
    def testCeilingAlarmAndStandardProcessing(self):
        self.process = ProcessTask.Create(self.e)
        spec = json.dumps({
            'processers': [{
                'calculation': 'MAX({speed})',
                'column': 'max_speed',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            }, {
                'calculation': '. + SUM({bearing})',
                'column': 'total_bearing',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            }, {
                'calculation': '. + COUNT({bearing})',
                'column': 'count_bearing',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            }, {
                'calculation': '. + COUNT(ALARMS())',
                'column': 'count_alarms',
                'analysis_key_pattern': ANALYSIS_KEY_PATTERN
            }]
        })
        self.process.Update(spec=spec,
                            rule_ids=[self.speeding_alarm.key().id()])
        self.process.put()

        # Apply our process to our sensor
        self.sp = SensorProcessTask.Create(self.e, self.process,
                                           self.vehicle_1)
        self.sp.put()

        BATCH_1 = {
            'speed': [
                0, 5, 15, 35, 60, 80, 83, 88, 85, 78, 75, 75, 76, 81, 89, 92,
                90, 83, 78
            ],  # We speed twice
            'bearing':
            [0, 0, 0, 0, 5, 3, 3, 3, 4, 5, 0, 0, 0, 0, 1, 1, 2, 3, 2]
        }
        self.__createNewRecords(BATCH_1,
                                first_dt=datetime.now() - timedelta(minutes=5))
        self.__runProcessing()

        # Confirm analyzed max speed
        a = Analysis.GetOrCreate(self.vehicle_1, ANALYSIS_KEY_PATTERN)
        self.assertIsNotNone(a)
        self.assertEqual(a.columnValue('max_speed'), max(BATCH_1['speed']))

        # Confirm we counted new alarms in analysis
        # self.assertEqual(a.columnValue('count_alarms'), 2) TODO: This fails!
        self.sp = SensorProcessTask.Get(self.process, self.vehicle_1)
        self.assertEqual(self.sp.status_last_run, PROCESS.OK)

        # Confirm speeding alarms (2)
        alarms = Alarm.Fetch(self.vehicle_1, self.speeding_alarm)
        self.assertEqual(len(alarms), 2)

        # Test alarm notifications
        # TODO: Test output of notification (e.g. log messages or contact records)
        a = alarms[0]  # second alarm
        message = a.render_alert_message(recipient=self.owner)
        SPEEDING_ALERT_MESSAGE_RENDERED = "Hello Dan Owner, %s was speeding at 81 at %s" % (
            TEST_SENSOR_ID,
            tools.sdatetime(a.dt_start, fmt="%H:%M", tz="Africa/Nairobi"))
        self.assertEqual(message, SPEEDING_ALERT_MESSAGE_RENDERED)

        BATCH_2 = {'speed': [76, 75, 78, 73, 60], 'bearing': [0, 0, 2, 0, 5]}
        self.__createNewRecords(BATCH_2)
        self.__runProcessing()

        a = Analysis.GetOrCreate(self.vehicle_1, ANALYSIS_KEY_PATTERN)
        self.assertEqual(a.columnValue('total_bearing'),
                         sum(BATCH_1['bearing']) + sum(BATCH_2['bearing']))
        self.assertEqual(a.columnValue('count_bearing'),
                         len(BATCH_1['bearing']) + len(BATCH_2['bearing']))
        self.assertEqual(a.columnValue('count_alarms'), 2)
        self.assertEqual(self.sp.status_last_run, PROCESS.OK)