Example #1
0
 def __init__(self, facilities=None, year=None, month=None):
     if not (year and month):
         self.month = datetime.utcnow().month
         self.year = datetime.utcnow().year
     else:
         self.month = month
         self.year = year
     if facilities is None:
         facilities = SupplyPoint.objects.filter(
             type__code="facility", contact__is_active=True).distinct()
     self.facilities = facilities
     self.report_month = self.month - 1 if self.month > 1 else 12
     self.report_year = self.year if self.report_month < 12 else self.year - 1
     self.dg = DeliveryGroups(month=month, facs=self.facilities)
     self._submission_chart = None
Example #2
0
    def testReminderSet(self):
        people = list(randr.get_facility_people(datetime.utcnow()))
        self.assertEqual(1, len(people))
        for person in people:
            self.assertEqual(self.contact, person)

        sp = self.contact.supply_point
        sp.groups = (SupplyPointGroup.objects.get\
                     (code=DeliveryGroups().current_delivering_group()),)
        sp.save()
        self.assertEqual(1, len(list(supervision.get_people())))

        supervision.set_supervision_statuses()

        self.assertEqual(0, len(list(supervision.get_people())))

        SupplyPointStatus.objects.all().delete()

        self.assertEqual(1, len(list(supervision.get_people())))

        s = SupplyPointStatus(
            supply_point=sp,
            status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
            status_value=SupplyPointStatusValues.RECEIVED)
        s.save()

        self.assertEqual(0, len(list(supervision.get_people())))
Example #3
0
 def setUp(self):
     super(TestSOHThankYou, self).setUp()
     Contact.objects.all().delete()
     self.contact = register_user(self, "778", "someone")
     sp = self.contact.supply_point
     sp.groups = (SupplyPointGroup.objects.get\
                  (code=DeliveryGroups().current_submitting_group()),)
     sp.save()
Example #4
0
def construct_randr_summary(supply_point):
    children = supply_point.children().filter(
        groups__code=DeliveryGroups().current_submitting_group())
    # assumes being run in the same month we care about.
    cutoff = randr.get_facility_cutoff()
    return _construct_status_dict(SupplyPointStatusTypes.R_AND_R_FACILITY, [
        SupplyPointStatusValues.SUBMITTED,
        SupplyPointStatusValues.NOT_SUBMITTED
    ], children, DateSpan(cutoff, datetime.utcnow()))
Example #5
0
    def testDeliveryGroupBasic(self):
        original_submitting = SupplyPoint.objects.filter\
            (type__code=SupplyPointCodes.FACILITY,
             groups__code=DeliveryGroups().current_submitting_group()).count()

        #add another submitting facility
        sp = SupplyPoint.objects.filter\
            (type__code=SupplyPointCodes.FACILITY).exclude\
            (groups__code=DeliveryGroups().current_submitting_group())[0]
        sp.groups = (SupplyPointGroup.objects.get\
                     (code=DeliveryGroups().current_submitting_group()),)
        sp.save()

        new_submitting = SupplyPoint.objects.filter\
            (type__code=SupplyPointCodes.FACILITY,
             groups__code=DeliveryGroups().current_submitting_group()).count()

        self.assertEqual(original_submitting + 1, new_submitting)
Example #6
0
 def setUp(self):
     super(TestDeliveryReminder, self).setUp()
     Contact.objects.all().delete()
     ProductReport.objects.all().delete()
     self.contact = register_user(self, "778", "someone")
     sp = self.contact.supply_point
     sp.groups = (SupplyPointGroup.objects.get\
                  (code=DeliveryGroups().current_delivering_group()),)
     sp.save()
Example #7
0
    def testGroupExclusion(self):
        people = list(delivery.get_facility_people(datetime.utcnow()))
        self.assertEqual(1, len(people))
        for person in people:
            self.assertEqual(self.contact, person)

        sp = self.contact.supply_point
        sp.groups = (SupplyPointGroup.objects.get\
                     (code=DeliveryGroups().current_submitting_group()),)
        sp.save()
        people = list(delivery.get_facility_people(datetime.utcnow()))
        self.assertEqual(0, len(people))

        sp = self.contact.supply_point
        sp.groups = (SupplyPointGroup.objects.get\
                     (code=DeliveryGroups().current_processing_group()),)
        sp.save()
        people = list(delivery.get_facility_people(datetime.utcnow()))
        self.assertEqual(0, len(people))
Example #8
0
def get_facility_people(cutoff):
    # Facilities:
    # Group A gets a reminder every three months starting in January.
    # Then it rotates accordingly.
    current_group = DeliveryGroups().current_submitting_group()

    for contact in Contact.objects.filter\
            (supply_point__type__code=SupplyPointCodes.FACILITY,
             supply_point__groups__code__in=current_group):
        if not contact.supply_point.supplypointstatus_set.filter\
                (status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                 status_date__gte=cutoff).exists():
            yield contact
Example #9
0
def sps_with_latest_status(sps, status_type, status_value, year, month):
    """
    This method is used by the dashboard.
    """
    # filter out people who submitted in the wrong month
    if status_type.startswith('rr'):
        sps = DeliveryGroups(month).submitting(sps, month)
    elif status_type.startswith('del'):
        sps = DeliveryGroups(month).delivering(sps, month)
    if not sps.count():
        return SupplyPoint.objects.none()
    inner = sps.filter(supplypointstatus__status_type=status_type,
                       supplypointstatus__status_date__month=month,
                       supplypointstatus__status_date__year=year)\
                        .annotate(max_sp_status_id=Max('supplypointstatus__id'))
    ids = SupplyPointStatus.objects.filter(status_type=status_type,
                                           status_value=status_value,
                                           id__in=inner.values('max_sp_status_id').query)\
                                           .distinct()\
                                            .values_list("supply_point", flat=True)
    f = sps.filter(id__in=ids)
    return f
Example #10
0
 def __init__(self, facilities=None, year=None, month=None):
     if not (year and month):
         self.month = datetime.utcnow().month
         self.year = datetime.utcnow().year
     else:
         self.month = month
         self.year = year
     if facilities is None:
         facilities = SupplyPoint.objects.filter(type__code="facility", contact__is_active=True).distinct()
     self.facilities = facilities
     self.report_month = self.month - 1 if self.month > 1 else 12
     self.report_year = self.year if self.report_month < 12 else self.year - 1
     self.dg = DeliveryGroups(month=month, facs=self.facilities)
     self._submission_chart = None
Example #11
0
def get_facility_people(cutoff):
    # Facilities:
    # Group A gets a reminder every three months starting in March.
    # Then it rotates accordingly.
    # All people at all Districts get all reminders each month.
    # For facilities the reminder should go out if we haven't received 
    # any status of type of del_fac
    current_group = DeliveryGroups().current_delivering_group()
    
    for contact in Contact.objects.filter\
            (supply_point__type__code=SupplyPointCodes.FACILITY,
             supply_point__groups__code__in=current_group):
        if not contact.supply_point.supplypointstatus_set.filter\
                (status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                 status_date__gte=cutoff).exists():
            yield contact
Example #12
0
def sps_with_latest_status(sps, status_type, status_value, year, month):
    """
    This method is used by the dashboard.
    """
    # filter out people who submitted in the wrong month
    if status_type.startswith('rr'):
        sps = DeliveryGroups(month).submitting(sps, month)
    elif status_type.startswith('del'):
        sps = DeliveryGroups(month).delivering(sps, month)
    if not sps.count():
        return SupplyPoint.objects.none()
    inner = sps.filter(supplypointstatus__status_type=status_type,
                       supplypointstatus__status_date__month=month,
                       supplypointstatus__status_date__year=year)\
                        .annotate(max_sp_status_id=Max('supplypointstatus__id'))
    ids = SupplyPointStatus.objects.filter(status_type=status_type,
                                           status_value=status_value,
                                           id__in=inner.values('max_sp_status_id').query)\
                                           .distinct()\
                                            .values_list("supply_point", flat=True)
    f = sps.filter(id__in=ids)
    return f
    def setUp(self):
        super(TestReportSummaryBase, self).setUp()
        Contact.objects.all().delete()
        ProductReport.objects.all().delete()

        self.district_contact = register_user(self, "777", "someone")
        sp = SupplyPoint.objects.get(name="TEST DISTRICT")
        sp.groups = (SupplyPointGroup.objects.get\
                     (code=DeliveryGroups().current_submitting_group()),)
        self.district_contact.supply_point = sp
        self.district_contact.save()

        contact1 = register_user(self, "778", "Test User1", "d10001", "VETA 1")
        contact2 = register_user(self, "779", "Test User2", "d10002", "VETA 2")
        contact3 = register_user(self, "780", "Test User3", "d10003", "VETA 3")
        self.facility_contacts = [contact1, contact2, contact3]
        for contact in self.facility_contacts:
            # make sure parentage is right
            self.assertEqual(contact.supply_point.supplied_by,
                             self.district_contact.supply_point)
            # make sure group is right
            contact.supply_point.groups = (SupplyPointGroup.objects.get\
                                           (code=self.relevant_group),)
            contact.supply_point.save()
Example #14
0
class SupplyPointStatusBreakdown(object):

    def __init__(self, facilities=None, year=None, month=None):
        if not (year and month):
            self.month = datetime.utcnow().month
            self.year = datetime.utcnow().year
        else:
            self.month = month
            self.year = year
        if facilities is None:
            facilities = SupplyPoint.objects.filter(type__code="facility", contact__is_active=True).distinct()
        self.facilities = facilities
        self.report_month = self.month - 1 if self.month > 1 else 12
        self.report_year = self.year if self.report_month < 12 else self.year - 1
        self.dg = DeliveryGroups(month=month, facs=self.facilities)
        self._submission_chart = None

    @property
    def submitted(self):
        return list(sps_with_latest_status(sps=self.dg.submitting(self.facilities),
                                                year=self.year, month=self.month,
                                                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                                                status_value=SupplyPointStatusValues.SUBMITTED))
    @property
    def submitted_on_time(self):
        return filter(lambda sp: randr_reported_on_time(sp, self.year, self.month) == OnTimeStates.ON_TIME, self.submitted)

    @property
    def submitted_late(self):
        return filter(lambda sp: randr_reported_on_time(sp, self.year, self.month) == OnTimeStates.LATE, self.submitted)

    @property
    def not_submitted(self):
        return list(sps_with_latest_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                                                 status_value=SupplyPointStatusValues.NOT_SUBMITTED))

    @property
    def submit_reminder_sent(self):
        return list(sps_with_latest_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                                                 status_value=SupplyPointStatusValues.REMINDER_SENT))
    @property
    def submit_not_responding(self):
        return list(set(self.submit_reminder_sent) - set(self.submitted) - set(self.not_submitted))


    @property
    def no_randr_data(self):
        return list(set(self.dg.submitting(self.facilities)) -
                    set(self.submitted_on_time) -
                    set(self.submitted_late) -
                    set(self.not_submitted) -
                    set(self.submit_not_responding))
    @property
    def delivery_received(self):
        return list(sps_with_latest_status(sps=self.dg.delivering(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                                                 status_value=SupplyPointStatusValues.RECEIVED))

    @property
    def delivery_not_received(self):
        return list(sps_with_latest_status(sps=self.dg.delivering(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                                                 status_value=SupplyPointStatusValues.NOT_RECEIVED))


    @property
    def delivery_reminder_sent(self):
        return list(sps_with_latest_status(sps=self.dg.delivering(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                                                 status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def delivery_not_responding(self):
        return list(set(self.delivery_reminder_sent) - set(self.delivery_received) - set(self.delivery_not_received))


    @property
    def supervision_received(self):
        return list(sps_with_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                                                 status_value=SupplyPointStatusValues.RECEIVED))

    @property
    def supervision_not_received(self):
        return list(sps_with_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                                                 status_value=SupplyPointStatusValues.NOT_RECEIVED))

    @property
    def supervision_reminder_sent(self):
        return list(sps_with_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                                                 status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def supervision_not_responding(self):
        return list(set(self.supervision_reminder_sent) - set(self.supervision_received) - set(self.supervision_not_received))


    @property
    def no_supervision_data(self):
        return list(set(self.dg.submitting(self.facilities)) -
                    set(self.supervision_received) -
                    set(self.supervision_not_received) -
                    set(self.supervision_reminder_sent) -
                    set(self.supervision_not_responding))

    @property
    def not_submitted(self):
        return list(sps_with_latest_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                                                 status_value=SupplyPointStatusValues.NOT_SUBMITTED))

    @property
    def submit_reminder_sent(self):
        return list(sps_with_latest_status(sps=self.dg.submitting(self.facilities),
                                                 year=self.year, month=self.month,
                                                 status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                                                 status_value=SupplyPointStatusValues.REMINDER_SENT))
    @property
    def submit_not_responding(self):
        return list(set(self.submit_reminder_sent) - set(self.submitted) - set(self.not_submitted))


    @property
    def soh_submitted(self):
        return list(sps_with_latest_status(sps=self.facilities, year=self.year, month=self.month,
                                                         status_type=SupplyPointStatusTypes.SOH_FACILITY,
                                                         status_value=SupplyPointStatusValues.SUBMITTED))

    @property
    def soh_on_time(self):
        return filter(lambda sp: soh_reported_on_time(sp, self.year, self.month) == OnTimeStates.ON_TIME, self.facilities)

    @property
    def soh_late(self):
        return filter(lambda sp: soh_reported_on_time(sp, self.year, self.month) == OnTimeStates.LATE, self.facilities)

    @property
    def soh_not_responding(self):
        return filter(lambda sp: soh_reported_on_time(sp, self.year, self.month) in (OnTimeStates.NO_DATA, OnTimeStates.INSUFFICIENT_DATA), self.facilities)

    @property
    def avg_lead_time(self):
        if not self.facilities: return "<span class='no_data'>None</span>"
        sum = timedelta(0)
        count = 0
        for f in self.facilities:
            lt = avg_past_lead_time(f)
            if lt:
                sum += lt
                count += 1
        if not count: return "<span class='no_data'>None</span>"
        return sum / count

    def _percent(self, fn=None, of=None):
        if not of:
            of= len(self.facilities)
        else:
            of = len(getattr(self.dg, of)(self.facilities))
        return format_percent(len(getattr(self, fn)), of)

    percent_randr_on_time = curry(_percent, fn='submitted_on_time', of='submitting')
    percent_randr_late = curry(_percent, fn='submitted_late', of='submitting')
    percent_randr_not_submitted = curry(_percent, fn='not_submitted', of='submitting')
    percent_randr_reminder_sent = curry(_percent, fn='submit_reminder_sent', of='submitting')
    percent_randr_not_responding = curry(_percent, fn='submit_not_responding', of='submitting')
    percent_randr_no_data = curry(_percent, fn='no_randr_data', of='submitting')

    percent_soh_on_time = curry(_percent, fn='soh_on_time')
    percent_soh_late = curry(_percent, fn='soh_late')
    percent_soh_not_responding = curry(_percent, fn='soh_not_responding')

    percent_supervision_received = curry(_percent, fn='supervision_received', of='submitting')
    percent_supervision_not_received = curry(_percent, fn='supervision_not_received', of='submitting')
    percent_supervision_reminder_sent = curry(_percent, fn='supervision_reminder_sent', of='submitting')
    percent_supervision_not_responding = curry(_percent, fn='supervision_not_responding', of='submitting')

    @property
    def stockouts_in_month(self):
        # NOTE: Uses the report month/year, not the current month/year.
        return [f for f in self.facilities if ProductReport.objects.filter(supply_point__pk=f.pk, quantity=0, report_date__month=self.report_month, report_date__year=self.report_year).count()]

    percent_stockouts_in_month = curry(_percent, fn='stockouts_in_month')

    def stocked_out_of(self, product=None, month=None, year=None):
        return [f for f in self.facilities if f.historical_stock(product, year, month, default_value=None) == 0]

    def stocked_out_of(self, product=None, month=None, year=None):
        return [f for f in self.facilities if f.historical_stock(product, year, month, default_value=None) == 0]

    def percent_stocked_out(self, product, year, month):
        # Is this pattern confusing?
        return format_percent(len(self.stocked_out_of(product=product, year=year, month=month)), len(self.facilities))

    def _response_rate(self, type=None):
        num = 0.0
        denom = 0.0
        for f in self.dg.submitting(self.facilities):
            hrr = historical_response_rate(f, type)
            if hrr:
                num += hrr[0]
                denom += 1
        if denom:
            return "%.1f%%" % ((num / denom) * 100.0)
        else:
            return "<span class='no_data'>None</span>"

    supervision_response_rate = curry(_response_rate, type=SupplyPointStatusTypes.SUPERVISION_FACILITY)
    randr_response_rate = curry(_response_rate, type=SupplyPointStatusTypes.R_AND_R_FACILITY)



    def submission_chart(self):
        graph_data = [
                {"display": _("Submitted On Time"),
                 "value": len(self.submitted_on_time),
                 "color": Colors.GREEN,
                 "description": "(%s) Submitted On Time (%s %s)" % \
                    (len(self.submitted_on_time), month_name[self.report_month], self.report_year)
                },
                {"display": _("Submitted Late"),
                 "value": len(self.submitted_late),
                 "color": "orange",
                 "description": "(%s) Submitted Late (%s %s)" % \
                    (len(self.submitted_late), month_name[self.report_month], self.report_year)
                },
                {"display": _("Haven't Submitted"),
                 "value": len(self.not_submitted),
                 "color": Colors.RED,
                 "description": "(%s) Haven't Submitted (%s %s)" % \
                    (len(self.not_submitted), month_name[self.report_month], self.report_year)
                },
                {"display": _("Didn't Respond"),
                 "value": len(self.submit_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.submit_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._submission_chart = PieChartData(_("R&R Submission Summary") + " (%s %s)" % (month_name[self.report_month], self.report_year), graph_data)
        return self._submission_chart

    def delivery_chart(self):
        graph_data = [
                {"display": _("Delivery Received"),
                 "value": len(self.delivery_received),
                 "color": Colors.GREEN,
                 "description": "(%s) Delivery Received (%s %s)" % \
                    (len(self.delivery_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Delivery Not Received"),
                 "value": len(self.delivery_not_received),
                 "color": Colors.RED,
                 "description": "(%s) Delivery Not Received (%s %s)" % \
                    (len(self.delivery_not_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Didn't Respond"),
                 "value": len(self.delivery_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.delivery_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._delivery_chart = PieChartData(_("Delivery Summary") + " (%s %s)" % (month_name[self.report_month], self.report_year), graph_data)
        return self._delivery_chart

    def soh_chart(self):
        graph_data = [
                {"display": _("Stock Report On Time"),
                 "value": len(self.soh_on_time),
                 "color": Colors.GREEN,
                 "description": "(%s) SOH On Time (%s %s)" % \
                    (len(self.soh_on_time), month_name[self.report_month], self.report_year)
                },
                {"display": _("Stock Report Late"),
                 "value": len(self.soh_late),
                 "color": "orange",
                 "description": "(%s) Submitted Late (%s %s)" % \
                    (len(self.soh_late), month_name[self.report_month], self.report_year)
                },
                {"display": _("SOH Not Responding"),
                 "value": len(self.soh_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.soh_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._soh_chart = PieChartData(_("SOH Submission Summary") + " (%s %s)" % (month_name[self.report_month], self.report_year), graph_data)
        return self._soh_chart

    def supervision_chart(self):
        graph_data = [
                {"display": _("Supervision Received"),
                 "value": len(self.supervision_received),
                 "color": Colors.GREEN,
                 "description": "(%s) Supervision Received (%s %s)" % \
                    (len(self.supervision_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Supervision Not Received"),
                 "value": len(self.supervision_not_received),
                 "color": Colors.RED,
                 "description": "(%s) Supervision Not Received (%s %s)" % \
                    (len(self.supervision_not_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Supervision Not Responding"),
                 "value": len(self.supervision_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.supervision_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._soh_chart = PieChartData(_("Supervision Summary") + " (%s %s)" % (month_name[self.report_month], self.report_year), graph_data)
        return self._soh_chart
 def relevant_group(self):
     return DeliveryGroups().current_submitting_group()
 def relevant_group(self):
     # this doesn't really matter since it's relevant every month
     return DeliveryGroups().current_delivering_group()
 def relevant_group(self):
     return DeliveryGroups().current_delivering_group()
Example #18
0
 def submitting_group(self):
     return DeliveryGroups(self.month).current_submitting_group()
Example #19
0
class SupplyPointStatusBreakdown(object):
    def __init__(self, facilities=None, year=None, month=None):
        if not (year and month):
            self.month = datetime.utcnow().month
            self.year = datetime.utcnow().year
        else:
            self.month = month
            self.year = year
        if facilities is None:
            facilities = SupplyPoint.objects.filter(
                type__code="facility", contact__is_active=True).distinct()
        self.facilities = facilities
        self.report_month = self.month - 1 if self.month > 1 else 12
        self.report_year = self.year if self.report_month < 12 else self.year - 1
        self.dg = DeliveryGroups(month=month, facs=self.facilities)
        self._submission_chart = None

    @property
    def submitted(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                status_value=SupplyPointStatusValues.SUBMITTED))

    @property
    def submitted_on_time(self):
        return filter(
            lambda sp: randr_reported_on_time(sp, self.year, self.month) ==
            OnTimeStates.ON_TIME, self.submitted)

    @property
    def submitted_late(self):
        return filter(
            lambda sp: randr_reported_on_time(sp, self.year, self.month) ==
            OnTimeStates.LATE, self.submitted)

    @property
    def not_submitted(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                status_value=SupplyPointStatusValues.NOT_SUBMITTED))

    @property
    def submit_reminder_sent(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def submit_not_responding(self):
        return list(
            set(self.submit_reminder_sent) - set(self.submitted) -
            set(self.not_submitted))

    @property
    def no_randr_data(self):
        return list(
            set(self.dg.submitting(self.facilities)) -
            set(self.submitted_on_time) - set(self.submitted_late) -
            set(self.not_submitted) - set(self.submit_not_responding))

    @property
    def delivery_received(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.delivering(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                status_value=SupplyPointStatusValues.RECEIVED))

    @property
    def delivery_not_received(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.delivering(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                status_value=SupplyPointStatusValues.NOT_RECEIVED))

    @property
    def delivery_reminder_sent(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.delivering(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.DELIVERY_FACILITY,
                status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def delivery_not_responding(self):
        return list(
            set(self.delivery_reminder_sent) - set(self.delivery_received) -
            set(self.delivery_not_received))

    @property
    def supervision_received(self):
        return list(
            sps_with_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                status_value=SupplyPointStatusValues.RECEIVED))

    @property
    def supervision_not_received(self):
        return list(
            sps_with_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                status_value=SupplyPointStatusValues.NOT_RECEIVED))

    @property
    def supervision_reminder_sent(self):
        return list(
            sps_with_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.SUPERVISION_FACILITY,
                status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def supervision_not_responding(self):
        return list(
            set(self.supervision_reminder_sent) -
            set(self.supervision_received) -
            set(self.supervision_not_received))

    @property
    def no_supervision_data(self):
        return list(
            set(self.dg.submitting(self.facilities)) -
            set(self.supervision_received) -
            set(self.supervision_not_received) -
            set(self.supervision_reminder_sent) -
            set(self.supervision_not_responding))

    @property
    def not_submitted(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                status_value=SupplyPointStatusValues.NOT_SUBMITTED))

    @property
    def submit_reminder_sent(self):
        return list(
            sps_with_latest_status(
                sps=self.dg.submitting(self.facilities),
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.R_AND_R_FACILITY,
                status_value=SupplyPointStatusValues.REMINDER_SENT))

    @property
    def submit_not_responding(self):
        return list(
            set(self.submit_reminder_sent) - set(self.submitted) -
            set(self.not_submitted))

    @property
    def soh_submitted(self):
        return list(
            sps_with_latest_status(
                sps=self.facilities,
                year=self.year,
                month=self.month,
                status_type=SupplyPointStatusTypes.SOH_FACILITY,
                status_value=SupplyPointStatusValues.SUBMITTED))

    @property
    def soh_on_time(self):
        return filter(
            lambda sp: soh_reported_on_time(sp, self.year, self.month) ==
            OnTimeStates.ON_TIME, self.facilities)

    @property
    def soh_late(self):
        return filter(
            lambda sp: soh_reported_on_time(sp, self.year, self.month) ==
            OnTimeStates.LATE, self.facilities)

    @property
    def soh_not_responding(self):
        return filter(
            lambda sp: soh_reported_on_time(sp, self.year, self.month) in
            (OnTimeStates.NO_DATA, OnTimeStates.INSUFFICIENT_DATA),
            self.facilities)

    @property
    def avg_lead_time(self):
        if not self.facilities: return "<span class='no_data'>None</span>"
        sum = timedelta(0)
        count = 0
        for f in self.facilities:
            lt = avg_past_lead_time(f)
            if lt:
                sum += lt
                count += 1
        if not count: return "<span class='no_data'>None</span>"
        return sum / count

    def _percent(self, fn=None, of=None):
        if not of:
            of = len(self.facilities)
        else:
            of = len(getattr(self.dg, of)(self.facilities))
        return format_percent(len(getattr(self, fn)), of)

    percent_randr_on_time = curry(_percent,
                                  fn='submitted_on_time',
                                  of='submitting')
    percent_randr_late = curry(_percent, fn='submitted_late', of='submitting')
    percent_randr_not_submitted = curry(_percent,
                                        fn='not_submitted',
                                        of='submitting')
    percent_randr_reminder_sent = curry(_percent,
                                        fn='submit_reminder_sent',
                                        of='submitting')
    percent_randr_not_responding = curry(_percent,
                                         fn='submit_not_responding',
                                         of='submitting')
    percent_randr_no_data = curry(_percent,
                                  fn='no_randr_data',
                                  of='submitting')

    percent_soh_on_time = curry(_percent, fn='soh_on_time')
    percent_soh_late = curry(_percent, fn='soh_late')
    percent_soh_not_responding = curry(_percent, fn='soh_not_responding')

    percent_supervision_received = curry(_percent,
                                         fn='supervision_received',
                                         of='submitting')
    percent_supervision_not_received = curry(_percent,
                                             fn='supervision_not_received',
                                             of='submitting')
    percent_supervision_reminder_sent = curry(_percent,
                                              fn='supervision_reminder_sent',
                                              of='submitting')
    percent_supervision_not_responding = curry(_percent,
                                               fn='supervision_not_responding',
                                               of='submitting')

    @property
    def stockouts_in_month(self):
        # NOTE: Uses the report month/year, not the current month/year.
        return [
            f for f in self.facilities if ProductReport.objects.filter(
                supply_point__pk=f.pk,
                quantity=0,
                report_date__month=self.report_month,
                report_date__year=self.report_year).count()
        ]

    percent_stockouts_in_month = curry(_percent, fn='stockouts_in_month')

    def stocked_out_of(self, product=None, month=None, year=None):
        return [
            f for f in self.facilities if f.historical_stock(
                product, year, month, default_value=None) == 0
        ]

    def stocked_out_of(self, product=None, month=None, year=None):
        return [
            f for f in self.facilities if f.historical_stock(
                product, year, month, default_value=None) == 0
        ]

    def percent_stocked_out(self, product, year, month):
        # Is this pattern confusing?
        return format_percent(
            len(self.stocked_out_of(product=product, year=year, month=month)),
            len(self.facilities))

    def _response_rate(self, type=None):
        num = 0.0
        denom = 0.0
        for f in self.dg.submitting(self.facilities):
            hrr = historical_response_rate(f, type)
            if hrr:
                num += hrr[0]
                denom += 1
        if denom:
            return "%.1f%%" % ((num / denom) * 100.0)
        else:
            return "<span class='no_data'>None</span>"

    supervision_response_rate = curry(
        _response_rate, type=SupplyPointStatusTypes.SUPERVISION_FACILITY)
    randr_response_rate = curry(_response_rate,
                                type=SupplyPointStatusTypes.R_AND_R_FACILITY)

    def submission_chart(self):
        graph_data = [
                {"display": _("Submitted On Time"),
                 "value": len(self.submitted_on_time),
                 "color": Colors.GREEN,
                 "description": "(%s) Submitted On Time (%s %s)" % \
                    (len(self.submitted_on_time), month_name[self.report_month], self.report_year)
                },
                {"display": _("Submitted Late"),
                 "value": len(self.submitted_late),
                 "color": "orange",
                 "description": "(%s) Submitted Late (%s %s)" % \
                    (len(self.submitted_late), month_name[self.report_month], self.report_year)
                },
                {"display": _("Haven't Submitted"),
                 "value": len(self.not_submitted),
                 "color": Colors.RED,
                 "description": "(%s) Haven't Submitted (%s %s)" % \
                    (len(self.not_submitted), month_name[self.report_month], self.report_year)
                },
                {"display": _("Didn't Respond"),
                 "value": len(self.submit_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.submit_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._submission_chart = PieChartData(
            _("R&R Submission Summary") + " (%s %s)" %
            (month_name[self.report_month], self.report_year), graph_data)
        return self._submission_chart

    def delivery_chart(self):
        graph_data = [
                {"display": _("Delivery Received"),
                 "value": len(self.delivery_received),
                 "color": Colors.GREEN,
                 "description": "(%s) Delivery Received (%s %s)" % \
                    (len(self.delivery_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Delivery Not Received"),
                 "value": len(self.delivery_not_received),
                 "color": Colors.RED,
                 "description": "(%s) Delivery Not Received (%s %s)" % \
                    (len(self.delivery_not_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Didn't Respond"),
                 "value": len(self.delivery_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.delivery_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._delivery_chart = PieChartData(
            _("Delivery Summary") + " (%s %s)" %
            (month_name[self.report_month], self.report_year), graph_data)
        return self._delivery_chart

    def soh_chart(self):
        graph_data = [
                {"display": _("Stock Report On Time"),
                 "value": len(self.soh_on_time),
                 "color": Colors.GREEN,
                 "description": "(%s) SOH On Time (%s %s)" % \
                    (len(self.soh_on_time), month_name[self.report_month], self.report_year)
                },
                {"display": _("Stock Report Late"),
                 "value": len(self.soh_late),
                 "color": "orange",
                 "description": "(%s) Submitted Late (%s %s)" % \
                    (len(self.soh_late), month_name[self.report_month], self.report_year)
                },
                {"display": _("SOH Not Responding"),
                 "value": len(self.soh_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.soh_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._soh_chart = PieChartData(
            _("SOH Submission Summary") + " (%s %s)" %
            (month_name[self.report_month], self.report_year), graph_data)
        return self._soh_chart

    def supervision_chart(self):
        graph_data = [
                {"display": _("Supervision Received"),
                 "value": len(self.supervision_received),
                 "color": Colors.GREEN,
                 "description": "(%s) Supervision Received (%s %s)" % \
                    (len(self.supervision_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Supervision Not Received"),
                 "value": len(self.supervision_not_received),
                 "color": Colors.RED,
                 "description": "(%s) Supervision Not Received (%s %s)" % \
                    (len(self.supervision_not_received), month_name[self.report_month], self.report_year)
                },
                {"display": _("Supervision Not Responding"),
                 "value": len(self.supervision_not_responding),
                 "color": Colors.PURPLE,
                 "description": "(%s) Didn't Respond (%s %s)" % \
                    (len(self.supervision_not_responding), month_name[self.report_month], self.report_year)
                }
            ]
        self._soh_chart = PieChartData(
            _("Supervision Summary") + " (%s %s)" %
            (month_name[self.report_month], self.report_year), graph_data)
        return self._soh_chart