Example #1
0
 def setUp(self):
     self.email = '*****@*****.**'
     self.password = '******'
     self.spreadsheet = 'Praekelt'
     self.reader = SimpleCRUD(self.email, self.password)
     self.assertTrue(self.reader)
     self.start = date.today() - timedelta(days=14)
     self.until = date.today()
     self.test_dict = {
                    1: {
                         'appointmentdate1': date(2011, 8, 1),
                         'fileno': 9999999,
                         'appointmentstatus1': 'Scheduled',
                         'phonenumber': 123456789
                       },
                    2: {
                        'appointmentdate1': date(2011, 8, 5),
                        'fileno': 8888888,
                        'appointmentstatus1': 'Scheduled',
                        'phonenumber': 987654321
                        },
                    3: {
                        'appointmentdate1': date(2011, 8, 11),
                        'fileno': 7777777,
                        'appointmentstatus1': 'Scheduled',
                        'phonenumber': 741852963
                        },
                    4: {
                        'appointmentdate1': date(2011, 9, 2),
                        'fileno': 6666666,
                        'appointmentstatus1': 'Scheduled',
                        'phonenumber': 369258147
                        }
     }
Example #2
0
    def __init__(self, owner, email, password):
        '''
        @arguments:
        email: The user's google email account username.
        password: The user's google account password.

        Uses google account details to login to the user's account.
        The account details are used by the spreadsheet reader class
        to perfom a ProgrammaticLogin on the user's account for
        spreadsheet access.
        '''
        self.owner = owner
        self.email = email
        self.password = password
        self.reader = SimpleCRUD(self.email, self.password)
Example #3
0
    def __init__(self, email, password):
        '''
        @arguments:
        email: The user's google email account username.
        password: The user's google account password.

        Uses google account details to login to the user's account.
        The account details are used by the spreadsheet reader class
        to perfom a ProgrammaticLogin on the user's account for
        spreadsheet access.
        '''
        self.email = email
        self.password = password
        self.reader = SimpleCRUD(self.email, self.password)
Example #4
0
class SpreadSheetReaderTestCase(TestCase):
    def setUp(self):
        self.email = '*****@*****.**'
        self.password = '******'
        self.spreadsheet = 'Praekelt'
        self.reader = SimpleCRUD(self.email, self.password)
        self.assertTrue(self.reader)
        self.start = date.today() - timedelta(days=14)
        self.until = date.today()
        self.test_dict = {
                       1: {
                            'appointmentdate1': date(2011, 8, 1),
                            'fileno': 9999999,
                            'appointmentstatus1': 'Scheduled',
                            'phonenumber': 123456789
                          },
                       2: {
                           'appointmentdate1': date(2011, 8, 5),
                           'fileno': 8888888,
                           'appointmentstatus1': 'Scheduled',
                           'phonenumber': 987654321
                           },
                       3: {
                           'appointmentdate1': date(2011, 8, 11),
                           'fileno': 7777777,
                           'appointmentstatus1': 'Scheduled',
                           'phonenumber': 741852963
                           },
                       4: {
                           'appointmentdate1': date(2011, 9, 2),
                           'fileno': 6666666,
                           'appointmentstatus1': 'Scheduled',
                           'phonenumber': 369258147
                           }
        }

    def tearDown(self):
        pass

    def test_get_spreadsheet(self):
        """Test for getting a spreadsheet that exists."""
        self.found = self.reader.get_spreadsheet(self.spreadsheet)
        self.assertTrue(self.found)

    def test_get_spreadsheet_fail(self):
        """Test for getting a spreadsheet that does not exists."""
        self.fail_spreadsheet = '##########'
        self.not_found = self.reader.get_spreadsheet(self.fail_spreadsheet)
        self.assertEqual(self.not_found, False)

    def test_appointment_rows(self):
        """
        Test for getting the appointments in a worksheet
        that fall between the from_date to end_date.
        """
        self.from_date = date(2011, 8, 1)
        self.end_date = date(2011, 8, 14)
        self.retrived_rows = self.reader.appointment_rows(
                                self.test_dict, self.from_date, self.end_date
        )
        self.assertEquals(len(self.retrived_rows), 3)
        self.assertEqual(self.retrived_rows[1], self.test_dict[1])
        self.assertEqual(self.retrived_rows[2], self.test_dict[2])
        self.assertEqual(self.retrived_rows[3], self.test_dict[3])

    def test_date_object_creator(self):
        """Convert date string to datetime object. """
        self.valid_dates = ['21/08/2011', '31/8/2011']
        self.curr_date = self.reader.date_object_creator('1/8/2011')
        self.assertTrue(self.curr_date)

    def test_database_record(self):
        """Convert worksheet row contents to proper types."""
        self.test_row = {
                            'appointmentdate1': '02/09/2011',
                            'fileno': '63601',
                            'appointmentstatus1': 'Scheduled',
                            'phonenumber': '969577542',
        }
        self.modified_row = self.reader.database_record(self.test_row)
        self.app_date = self.modified_row['appointmentdate1']
        self.app_status = self.modified_row['appointmentstatus1']
        self.file_no = self.modified_row['fileno']
        self.phone = self.modified_row['phonenumber']

        #test if the fields where converted correctly
        self.assertEquals(self.app_date, date(2011, 9, 2))
        self.assertEquals(self.app_status, self.test_row['appointmentstatus1'])
        self.assertEquals(self.file_no, '63601')
        self.assertEquals(self.phone, 969577542)

        #test if the received fields are equal to those sent
        self.app_date = str(self.app_date)
        self.app_date = self.reader.date_format(self.app_date)
        self.assertEquals(self.app_date, self.test_row['appointmentdate1'])
        self.app_status = str(self.app_status)
        self.assertEquals(self.app_status, self.test_row['appointmentstatus1'])
        self.file_no = str(self.file_no)
        self.assertEquals(self.file_no, self.test_row['fileno'])
        self.phone = str(self.phone)
        self.assertEquals(self.phone, self.test_row['phonenumber'])

    def test_run_enrollment_check(self):
        """Tests if the patient has enrolled """
        self.enrol = self.reader.run_enrollment_check(
                          self.spreadsheet, 63601, self.start, self.until
        )
        self.assertEquals(self.enrol, True)

    def test_not_enrolled(self):
        """Test for a patient that is not enrolled. """
        self.not_enrol = self.reader.run_enrollment_check(
                                self.spreadsheet, 60001, self.start, self.until
        )
        self.assertEquals(self.not_enrol, False)

    def test_run_appointment_check(self):
        """Test if the appointments worksheets are retrieved."""
        self.month = self.reader.run_appointment(
                                       self.spreadsheet, self.start, self.until
        )
        self.assertTrue(self.month)
Example #5
0
class Importer(object):
    def __init__(self, owner, email, password):
        '''
        @arguments:
        email: The user's google email account username.
        password: The user's google account password.

        Uses google account details to login to the user's account.
        The account details are used by the spreadsheet reader class
        to perfom a ProgrammaticLogin on the user's account for
        spreadsheet access.
        '''
        self.owner = owner
        self.email = email
        self.password = password
        self.reader = SimpleCRUD(self.email, self.password)

    def import_spread_sheet(self, doc_name, start, until):
        """
        @arguments:
        doc_name: the name of spreadsheet to import data from.
        start: indicates the date to start import data from.
        until: indicates the date import data function must stop at.

        This reads data from a google spreadsheet.
        If the data to be read from a spreadsheet is from
        two worksheets then each worksheet is stored on a
        sparate dictionary. For two worksheet the self.month will have
        two dictionaries. Patient updates are done for each worksheet.

        self.month: stores the complete spreadsheet(has worksheets(s))
        """
        self.start = start
        self.until = until
        self.doc_name = str(doc_name)
        self.month = self.reader.run_appointment(self.doc_name, start, until)
        #counts how many enrolled patients where updated correctly
        correct_updates = 0
        #counter for number of patients found on the enrollement worksheet
        enrolled_counter = 0
        #check if a worksheet was returned
        if self.month == False:
            logging.exception("Incorrect spreadsheet or worksheet name")
            #indicates if the spreadsheet name was found in the database
            valid_name = False
            #return the name of invalid spreadsheet and the flag
            return (self.doc_name, valid_name)
        #if spreadsheet was found check if it has more than one worksheet
        else:
            if len(self.month) > 0:
                #loop through the worksheets in the spreadsheet
                for worksheet in self.month:
                    #check that the spreadsheet has data to update
                    if len(self.month[worksheet]) != 0:
                        #if true do update each enrolled patient
                        #enrolled_counter, correct_updates = self.update_patients(
                            #self.month[worksheet], self.doc_name, start, until
                        #)
                        #return enrolled and updated counters
                        return enrolled_counter, correct_updates
                    #if the worksheet does not have data dont do updates
                    else:
                        logging.exception("The are no patient's to update")
                        #flag for checking if the spreadsheet has data
                        data = False
                        #return empty spreadsheet name and flag
                        return enrolled_counter, correct_updates
            else:
                logging.exception("The spreadsheet is empty.")
                data = False
                return (self.doc_name, data)

    def update_patients(self, month_worksheet, doc_name, start, until):
        """
        @arguments:
        month_worksheet: store the current month's worksheet from spreadsheet.
        doc_name: the name of spreadsheet to import data from.
        start: indicates the date to start import data from.
        until: indicates the date import data function must stop at.

        The method loops through each patient row in the
        worksheet, It gets the file no for the current patient
        and checks if the patient has enrolled to use appointment service.
        If patient was found in the enrollment worksheet then perfom updates
        else log error that the patient needs to be enrolled.
        """
        #counts how many enrolled patients where updated correctly
        correct_updates = 0
        #counter for number of patients found on the enrollement worksheet
        enrolled_counter = 0
        #loop for checking which patient details need to be updated
        for patient in month_worksheet:
            file_no = month_worksheet[patient]['fileno']
            #call method to get the cached enrollemnt status for the patient
            enrolled = self.get_cache_enrollement_status(doc_name, file_no)
            #check if the cache has the patient's enrollment status
            if enrolled:
                logging.debug("Patient's enrollment status cached")
                #check if the patient was enrolled
                if enrolled == True:
                    logging.debug("Patient: %s status is True" % file_no)
                    #update the patient
                    update_flag = self.update_patient(
                                    month_worksheet[patient],
                                    patient, doc_name, start, until
                    )
                    enrolled_counter = enrolled_counter + 1
                    if update_flag == True:
                        correct_updates = correct_updates + 1
                        logging.debug(
                            "Cached enrollment status for patient: %s" %
                            file_no
                        )

                #check if patient needs to enroll
                elif enrolled is False:
                    logging.exception(
                    'Patient: %s cannot update with cached enrollment status' %
                    file_no
                    )
            #cache patient's appointment status if it was not cached.
            else:
                #call method to set cache
                cache_status = self.set_cache_enrollement_status(
                                           doc_name, file_no, start, until
                )
                #check if the patient is enrolled
                if cache_status == True:
                    #add to the enrolled patient counter
                    enrolled_counter = enrolled_counter + 1
                    #update enrolled patient
                    update_flag = self.update_patient(
                                    month_worksheet[patient],
                                    patient, doc_name, start, until
                    )
                    if update_flag == True:
                        correct_updates = correct_updates + 1
                        logging.debug("Updating the patient: %s" % file_no)
                #else, patient not enrolled
                else:
                    logging.exception(
                    'Patient: %s cannot update with False enrollment status' %
                    file_no
                    )

        return (enrolled_counter, correct_updates)

    def cache_key(self, *args):
        return hashlib.md5(''.join(args)).hexdigest()

    def set_cache_enrollement_status(self, doc_name, file_no, start, until):
         #check if the patient has enrolled
        if self.reader.run_enrollment_check(doc_name, file_no,
                                            start, until) is True:
            #cache the enrollment check
            cache.set(self.cache_key(doc_name, file_no), True, CACHE_TIMEOUT)
            logging.debug("Caching True status for patient: %s" %
                           file_no
            )
            #the patient is enrolled, cache true for enrollement status
            enrolled_cache = True
            return enrolled_cache
        elif self.reader.run_enrollment_check(doc_name, file_no,
                                              start, until) is False:
            #cache the enrollment check
            cache.set(self.cache_key(doc_name, file_no), False, CACHE_TIMEOUT)
            logging.exception(
                               "Caching False status for patient: %s" %
                               file_no
            )
            #the patient is not enrolled, cache False for enrollement status
            enrolled_cache = False
            return enrolled_cache

    def get_cache_enrollement_status(self, doc_name, file_no):
        #check if the enrollment check was cached
        enrolled = cache.get(self.cache_key(doc_name, file_no))
        return enrolled

    def update_patient(self, patient_row, row, doc_name, start, until):
        '''
        @rguments:
        patient_row: A row that contains a patients appointment info.
        row: The number of the row on the worksheet

        This method compares the contents of the patient row
        to what is in the database and
        make the appropriate updates if they are needed.

        The method checks if the patient is on the database;
        uses the file no and row no combo as the te_id key.

        @returns:
        patient_update: successful updates flag.
        '''
        #check that the arguments are proper types
        if type(row) == int and type(patient_row) == dict:
            row_no = row
            #get the contents of the row
            file_no, file_format = self.check_file_no_format(
                                                         patient_row['fileno']
            )
            phone, phone_format = self.check_msisdn_format(
                                                    patient_row['phonenumber']
            )
            app_date = patient_row['appointmentdate1']
            app_status, status_format = self.check_appointment_status(
                                            patient_row['appointmentstatus1']
            )
        #check if the data to be updated is in the correct formats
        if file_format and phone_format and status_format:
            if row_no < 10:
                row_no = '0' + str(row_no)
                visit_id = str(row_no) + '-' + file_no
            else:
                visit_id = str(row_no) + '-' + file_no

            #try to get the current patient from the database
            try:
                #try to locate the patient on the database
                curr_patient = Patient.objects.get(te_id=file_no)
            except Patient.DoesNotExist:
                #log error in import log
                logging.debug("Patient: %s not found in database" % file_no)
                #create a new patient
                created = self.create_patient(
                                             patient_row, row_no, doc_name,
                                             start, until
                )
                return created

            #if patient exist perform update and appointment date is valid
            if curr_patient:
                #get or create a clinic
                clinic = self.get_or_create_clinic(doc_name)
                #call method to do appointment status update
                app_update = self.update_appointment_status(
                        app_status, curr_patient, app_date, visit_id, clinic
                )
                #call method to update phone number
                phone_update = self.update_msisdn(phone, curr_patient)
                #check if no error occured during patient updated
                if app_update or phone_update:
                    patient_update = True
                    return  (patient_update)
                else:
                    patient_update = False
                    return  (patient_update)
        #if any of the format are incorrect dont update patient
        else:
            logging.exception("Invalid data format for patient: %s" % file_no)
            patient_update = False
            return patient_update

    def check_appointment_status(self, status):
        status_format = False
        status_options = ['missed', 'scheduled', 'attended', 'rescheduled']
        #check if the status is a string
        try:
            #check if the correct format
            match = STATUS_RE.match(status)
            try:
                #return the string to check
                app_status = match.group()
                app_status = app_status.lower()
                #check if the status is in the valid options list
                for valid_status in status_options:
                    #check if the status is valid
                    if valid_status == app_status:
                        status_format = True
            except AttributeError:
                logging.exception("Appointment status invalid format")
        except TypeError:
            logging.exception("Appointment status is not characters")

        return (status, status_format)

    def check_file_no_format(self, file_number):
        #ensure the file number is a string type
        file_no = str(file_number)
        #check the format of the file number
        try:
            #check file number format
            match = FILE_NO.match(file_no)
            try:
                #get string that matched pattern
                file_no = match.group()
                correct_format = True
                #return the correct file number
                return (file_no, correct_format)
            except AttributeError:
                logging.exception(
                     "File number can be combination of numbers and characters"
                )
                correct_format = False
                return (file_number, correct_format)
        except TypeError:
            logging.exception(
                     "File number can be combination of numbers and characters"
            )
            correct_format = False
            return (file_number, correct_format)

    def check_msisdn_format(self, phone):
        msisdn = str(phone)
        msisdn = msisdn.lstrip('0')
        msisdn = msisdn.lstrip('+')
        #check if the phone is the correct format
        if len(msisdn) == 9:
            try:
                #check if the user phone number is in the correct format
                match = PHONE_RE.match(msisdn)
                try:
                    #get the phone number
                    phone_number = match.group()
                    correct_format = True
                    phone_number = '27' + phone_number
                    return (phone_number, correct_format)
                except AttributeError:
                    logging.exception("invalid msisdn format.")
                    updated = False
                    return (phone, updated)
            except TypeError:
                logging.exception("The phone number must be in string format")
                phone_update = False
                return (phone, phone_update)
        #check if the phone is in internation format
        elif len(msisdn) == 11:
            try:
                #check if the user phone number is in the correct format
                match = MSISDNS_RE.match(msisdn)
                try:
                    #get the phone number
                    phone_number = match.group()
                    correct_format = True
                    return (phone_number, correct_format)
                except AttributeError:
                    logging.exception("invalid msisdn format.")
                    updated = False
                    return (phone, updated)
            except TypeError:
                logging.exception("The phone number must be in string format")
                phone_update = False
                return (phone, phone_update)
        else:
            logging.exception("The phone number must be 9 to 11 digits")
            phone_update = False
            return (phone, phone_update)

    def create_patient(self, patient_row, row_no, doc_name, start, until):
        #get the contents of the row
        file_no, file_format = self.check_file_no_format(patient_row['fileno'])
        phone, phone_format = self.check_msisdn_format(
                                                patient_row['phonenumber']
        )
        app_date = patient_row['appointmentdate1']
        app_status = patient_row['appointmentstatus1']
        #check if the file number is correct format
        if file_format:
            #visit id
            if row_no < 10:
                row_no = '0' + str(row_no)
                visit_id = str(row_no) + '-' + file_no
            else:
                visit_id = str(row_no) + '-' + file_no
            #get the owner
            # owner = self.get_or_create_owner('googledoc')
            #if phone format is valid create patient's msisdn
            if phone_format:
                #create or get phone number
                msisdn, msisdn_created = self.get_or_create_msisdn(phone)
            #check if the patient field are valid formats
            if self.owner and msisdn and file_no:
                #try to create a patient with the contents of the worksheet row
                try:
                    #create a new patient
                    new_patient = Patient(
                                              te_id=file_no,
                                              active_msisdn=msisdn,
                                              owner=self.owner
                    )
                    #save to database
                    new_patient.save()
                    patient_created = True
                    # add msisdn to list of msisdns
                    new_patient.msisdns.add(msisdn)
                    #get or create a clinic
                    clinic = self.get_or_create_clinic(doc_name)
                    enrolled = self.set_cache_enrollement_status(
                                    doc_name, file_no, start, until
                    )
                    #check if the enrollment check was cached
                    enrolled = cache.get(file_no)
                    #convert string enroment to a choice key used in database
                    status = self.update_needed(app_status)
                    #if patient is enrolled create a visit instanceprint
                    if enrolled and patient_created and clinic:
                        #call method to create a visit for the new patient
                        visit, visit_created = self.create_visit(
                                                    visit_id, new_patient,
                                                    app_date, status, clinic
                        )
                        if visit_created:
                            #indicate that patient and visit was created
                            created = True
                            logging.debug("Created patient's visit")
                            return created
                    elif not enrolled:
                        #the patient was created and erollment status is false
                        return patient_created
                #catch relational integrity error
                except IntegrityError:
                    logging.exception("Failed to create patient invalid field")
                    patient_created = False
                    return patient_created
        #if any of the patient data was incorrect dont create patient
        else:
            logging.debug("Error creating patient with invalid file number")
            #indicate that patient could not be created becuase of the error
            created = False
            return created

    def create_visit(self, visit_id, new_patient, app_date, status, clinic):
        #use spreadsheet data to create visit
        try:
            #create a the visit
            new_visit = Visit(
                                 te_visit_id=visit_id,
                                 patient=new_patient,
                                 date=app_date,
                                 status=status,
                                 clinic=clinic
            )
            new_visit.save()
            logging.debug("Created the visit %s for new patien" % visit_id)
            visit_created = True
            visit = new_visit
        except IntegrityError:
            logging.exception("Failed to create visit %s." % visit_id)
            visit_created = False
            visit = None
        return (visit, visit_created)

    def get_or_create_clinic(self, doc_name):
        #get or create a clinic with the name of the spreadsheet
        clinic, created = Clinic.objects.get_or_create(name=doc_name,
            user=self.owner)
        return clinic

    def get_or_create_owner(self, name):
         #get the user or create one
        owner, owner_created = User.objects.get_or_create(username=name)
        return owner

    def get_or_create_msisdn(self, phone):
        #create a msisdn
        msisdn, msisdn_created = MSISDN.objects.get_or_create(msisdn=phone)
        return (msisdn, msisdn_created)

    def update_msisdn(self, msisdn, curr_patient):
        '''
        @rguments:
        msisdn: the phone number to be updated
        curr_patient: is the patient who's phone number is being updated.

        The method checks if the phone number is in the correct format.
        Correct format for msisdn is: 123456789 / 27123456789
        If format is correct retrive it from database if not found
        create one and add it to the patient msisdns field.

        stores phone number in 27123456789 in the database

        @returns:
        created: Indicate whether an phone number update occurred
        '''
        phone, phone_format = self.check_msisdn_format(msisdn)
        if phone_format is True:
            #update the patient phone number
            phone, created = MSISDN.objects.get_or_create(msisdn=msisdn)
            if created:
                #check if the phone number is not on the list of MSISDN add it
                if phone not in curr_patient.msisdns.all():
                    curr_patient.msisdns.add(phone)
                    #set the patient's active msisdn to recently added msisdn
                    curr_patient.active_msisdn = phone
                logging.debug('Phone number update for patient: %s' %
                               curr_patient
                )
                return (phone.msisdn, created)
            elif not created:
                logging.debug('Phone number has not changed for patient: %s' %
                               curr_patient)
                return (phone.msisdn, created)
        # if the msisdn does not have correct phone number format log error
        else:
            logging.exception('invalid phone number format for patient: %s' %
                              curr_patient)
            phone_update = False
            return (msisdn, phone_update)

    def update_needed(self, status):
        """
        @rguments:
        status: appointment status.

        Converts the appointment status
        variable to the length of the
        one stored on the database.

        @returns:
        database_status: appointment status a letter.
        """

        if status == 'Missed':
            return 'm'
        elif status == 'Rescheduled':
            return 'r'
        elif status == 'Attended':
            return 'a'
        elif status == 'Scheduled':
            return 's'

    def update_appointment_status(self, app_status, new_patient,
                                  app_date, visit_id, clinic_name):
        """
        @rgument:
        app_status: appointment status.
        app_date: appointment date.
        visit_id: database unique identifier for the patient

        The method begins by checking if the patient has made an appointment.
        If apointment was made check that the date has not changed.
        If the date in worksheet is more than that in the database
        then the appointment has been rescheduled if it is equal
        to it check if the user has attended a re/scheduled appointment,
        if it was missed modify to missed.

        Use logging for error reports and debugging.

        @returns:
        updated: indicates whether the appointment status was updated.
        """
        #converts a string enroment to a choice key used in database
        status = self.update_needed(app_status)
        try:
            #get patient's visit instance
            curr_visit = Visit.objects.get(te_visit_id=visit_id)
        except Visit.DoesNotExist:
            #log error in import log
            logging.debug("Creating a new visit for patient")
            if type(clinic_name) == str:
                clinic = self.get_or_create_clinic(clinic_name)
            else:
                clinic = clinic_name
            curr_visit, visit_created = self.create_visit(
                                                  visit_id, new_patient,
                                                  app_date, status, clinic
            )
            #check if the visit was created
            if not visit_created:
                return status

        #stores variable used to check if appointment updates are needed
        progress = self.update_needed(app_status)
        #dont update if the status has not changed
        if progress == curr_visit.status:
            logging.debug("The appointment status does not require an update")
            return status

        #if visit exist update to current status
        if curr_visit:
            #if appointment date is coming or is now update status
            if curr_visit.date >= app_date:
                #check if the user has attended the scheduled appointment
                if app_status == 'Attended':
                    #check if it was scheduled or rescheduled
                    if curr_visit.status == 's' or curr_visit.status == 'r':
                        #if true change to attended
                        try:
                            #save new appointment status
                            curr_visit.status = 'a'
                            curr_visit.save()
                            logging.debug('Patient %s attended appointment' %
                                          visit_id)
                            return curr_visit.status
                        #except save fial
                        except IntegrityError:
                            logging.exception("Appointment failed to update")
                            return curr_visit.status

                #if the user has missed a scheduled or rescheduled appointment
                if app_status == 'Missed':
                    if curr_visit.status == 's' or curr_visit.status == 'r':
                        try:
                            curr_visit.status = 'm'
                            curr_visit.save()
                            logging.debug('Patient missed appointment %s' %
                                          visit_id)
                            return curr_visit.status
                        except:
                            logging.exception("Appointment failed to update")
                            return curr_visit.status

            #check if the patient appointment date has passed
            elif curr_visit.date < app_date:
                #check if the patient has rescheduled
                if app_status == 'Rescheduled' and curr_visit.status == 's':
                    curr_visit.status = 'r'
                    try:
                        #change date to be the new rescheduled appointment date
                        curr_visit.date = app_date
                        curr_visit.save()
                        logging.debug('Patient %s rescheduled' % visit_id)
                        return curr_visit.status
                    except:
                        logging.exception("Appointment failed to update")
                        return curr_visit.status
Example #6
0
class Importer(object):
    def __init__(self, owner, email, password):
        '''
        @arguments:
        email: The user's google email account username.
        password: The user's google account password.

        Uses google account details to login to the user's account.
        The account details are used by the spreadsheet reader class
        to perfom a ProgrammaticLogin on the user's account for
        spreadsheet access.
        '''
        self.owner = owner
        self.email = email
        self.password = password
        self.reader = SimpleCRUD(self.email, self.password)

    def import_spread_sheet(self, doc_name, start, until):
        """
        @arguments:
        doc_name: the name of spreadsheet to import data from.
        start: indicates the date to start import data from.
        until: indicates the date import data function must stop at.

        This reads data from a google spreadsheet.
        If the data to be read from a spreadsheet is from
        two worksheets then each worksheet is stored on a
        sparate dictionary. For two worksheet the self.month will have
        two dictionaries. Patient updates are done for each worksheet.

        self.month: stores the complete spreadsheet(has worksheets(s))
        """
        self.start = start
        self.until = until
        self.doc_name = str(doc_name)
        self.month = self.reader.run_appointment(self.doc_name, start, until)
        #counts how many enrolled patients where updated correctly
        correct_updates = 0
        #counter for number of patients found on the enrollement worksheet
        enrolled_counter = 0
        #check if a worksheet was returned
        if self.month == False:
            logging.exception("Incorrect spreadsheet or worksheet name")
            #indicates if the spreadsheet name was found in the database
            valid_name = False
            #return the name of invalid spreadsheet and the flag
            return (self.doc_name, valid_name)
        #if spreadsheet was found check if it has more than one worksheet
        else:
            if len(self.month) > 0:
                #loop through the worksheets in the spreadsheet
                for worksheet in self.month:
                    #check that the spreadsheet has data to update
                    if len(self.month[worksheet]) != 0:
                        #if true do update each enrolled patient
                        #enrolled_counter, correct_updates = self.update_patients(
                        #self.month[worksheet], self.doc_name, start, until
                        #)
                        #return enrolled and updated counters
                        return enrolled_counter, correct_updates
                    #if the worksheet does not have data dont do updates
                    else:
                        logging.exception("The are no patient's to update")
                        #flag for checking if the spreadsheet has data
                        data = False
                        #return empty spreadsheet name and flag
                        return enrolled_counter, correct_updates
            else:
                logging.exception("The spreadsheet is empty.")
                data = False
                return (self.doc_name, data)

    def update_patients(self, month_worksheet, doc_name, start, until):
        """
        @arguments:
        month_worksheet: store the current month's worksheet from spreadsheet.
        doc_name: the name of spreadsheet to import data from.
        start: indicates the date to start import data from.
        until: indicates the date import data function must stop at.

        The method loops through each patient row in the
        worksheet, It gets the file no for the current patient
        and checks if the patient has enrolled to use appointment service.
        If patient was found in the enrollment worksheet then perfom updates
        else log error that the patient needs to be enrolled.
        """
        #counts how many enrolled patients where updated correctly
        correct_updates = 0
        #counter for number of patients found on the enrollement worksheet
        enrolled_counter = 0
        #loop for checking which patient details need to be updated
        for patient in month_worksheet:
            file_no = month_worksheet[patient]['fileno']
            #call method to get the cached enrollemnt status for the patient
            enrolled = self.get_cache_enrollement_status(doc_name, file_no)
            #check if the cache has the patient's enrollment status
            if enrolled:
                logging.debug("Patient's enrollment status cached")
                #check if the patient was enrolled
                if enrolled == True:
                    logging.debug("Patient: %s status is True" % file_no)
                    #update the patient
                    update_flag = self.update_patient(month_worksheet[patient],
                                                      patient, doc_name, start,
                                                      until)
                    enrolled_counter = enrolled_counter + 1
                    if update_flag == True:
                        correct_updates = correct_updates + 1
                        logging.debug(
                            "Cached enrollment status for patient: %s" %
                            file_no)

                #check if patient needs to enroll
                elif enrolled is False:
                    logging.exception(
                        'Patient: %s cannot update with cached enrollment status'
                        % file_no)
            #cache patient's appointment status if it was not cached.
            else:
                #call method to set cache
                cache_status = self.set_cache_enrollement_status(
                    doc_name, file_no, start, until)
                #check if the patient is enrolled
                if cache_status == True:
                    #add to the enrolled patient counter
                    enrolled_counter = enrolled_counter + 1
                    #update enrolled patient
                    update_flag = self.update_patient(month_worksheet[patient],
                                                      patient, doc_name, start,
                                                      until)
                    if update_flag == True:
                        correct_updates = correct_updates + 1
                        logging.debug("Updating the patient: %s" % file_no)
                #else, patient not enrolled
                else:
                    logging.exception(
                        'Patient: %s cannot update with False enrollment status'
                        % file_no)

        return (enrolled_counter, correct_updates)

    def cache_key(self, *args):
        return hashlib.md5(''.join(args)).hexdigest()

    def set_cache_enrollement_status(self, doc_name, file_no, start, until):
        #check if the patient has enrolled
        if self.reader.run_enrollment_check(doc_name, file_no, start,
                                            until) is True:
            #cache the enrollment check
            cache.set(self.cache_key(doc_name, file_no), True, CACHE_TIMEOUT)
            logging.debug("Caching True status for patient: %s" % file_no)
            #the patient is enrolled, cache true for enrollement status
            enrolled_cache = True
            return enrolled_cache
        elif self.reader.run_enrollment_check(doc_name, file_no, start,
                                              until) is False:
            #cache the enrollment check
            cache.set(self.cache_key(doc_name, file_no), False, CACHE_TIMEOUT)
            logging.exception("Caching False status for patient: %s" % file_no)
            #the patient is not enrolled, cache False for enrollement status
            enrolled_cache = False
            return enrolled_cache

    def get_cache_enrollement_status(self, doc_name, file_no):
        #check if the enrollment check was cached
        enrolled = cache.get(self.cache_key(doc_name, file_no))
        return enrolled

    def update_patient(self, patient_row, row, doc_name, start, until):
        '''
        @rguments:
        patient_row: A row that contains a patients appointment info.
        row: The number of the row on the worksheet

        This method compares the contents of the patient row
        to what is in the database and
        make the appropriate updates if they are needed.

        The method checks if the patient is on the database;
        uses the file no and row no combo as the te_id key.

        @returns:
        patient_update: successful updates flag.
        '''
        #check that the arguments are proper types
        if type(row) == int and type(patient_row) == dict:
            row_no = row
            #get the contents of the row
            file_no, file_format = self.check_file_no_format(
                patient_row['fileno'])
            phone, phone_format = self.check_msisdn_format(
                patient_row['phonenumber'])
            app_date = patient_row['appointmentdate1']
            app_status, status_format = self.check_appointment_status(
                patient_row['appointmentstatus1'])
        #check if the data to be updated is in the correct formats
        if file_format and phone_format and status_format:
            if row_no < 10:
                row_no = '0' + str(row_no)
                visit_id = str(row_no) + '-' + file_no
            else:
                visit_id = str(row_no) + '-' + file_no

            #try to get the current patient from the database
            try:
                #try to locate the patient on the database
                curr_patient = Patient.objects.get(te_id=file_no)
            except Patient.DoesNotExist:
                #log error in import log
                logging.debug("Patient: %s not found in database" % file_no)
                #create a new patient
                created = self.create_patient(patient_row, row_no, doc_name,
                                              start, until)
                return created

            #if patient exist perform update and appointment date is valid
            if curr_patient:
                #get or create a clinic
                clinic = self.get_or_create_clinic(doc_name)
                #call method to do appointment status update
                app_update = self.update_appointment_status(
                    app_status, curr_patient, app_date, visit_id, clinic)
                #call method to update phone number
                phone_update = self.update_msisdn(phone, curr_patient)
                #check if no error occured during patient updated
                if app_update or phone_update:
                    patient_update = True
                    return (patient_update)
                else:
                    patient_update = False
                    return (patient_update)
        #if any of the format are incorrect dont update patient
        else:
            logging.exception("Invalid data format for patient: %s" % file_no)
            patient_update = False
            return patient_update

    def check_appointment_status(self, status):
        status_format = False
        status_options = ['missed', 'scheduled', 'attended', 'rescheduled']
        #check if the status is a string
        try:
            #check if the correct format
            match = STATUS_RE.match(status)
            try:
                #return the string to check
                app_status = match.group()
                app_status = app_status.lower()
                #check if the status is in the valid options list
                for valid_status in status_options:
                    #check if the status is valid
                    if valid_status == app_status:
                        status_format = True
            except AttributeError:
                logging.exception("Appointment status invalid format")
        except TypeError:
            logging.exception("Appointment status is not characters")

        return (status, status_format)

    def check_file_no_format(self, file_number):
        #ensure the file number is a string type
        file_no = str(file_number)
        #check the format of the file number
        try:
            #check file number format
            match = FILE_NO.match(file_no)
            try:
                #get string that matched pattern
                file_no = match.group()
                correct_format = True
                #return the correct file number
                return (file_no, correct_format)
            except AttributeError:
                logging.exception(
                    "File number can be combination of numbers and characters")
                correct_format = False
                return (file_number, correct_format)
        except TypeError:
            logging.exception(
                "File number can be combination of numbers and characters")
            correct_format = False
            return (file_number, correct_format)

    def check_msisdn_format(self, phone):
        msisdn = str(phone)
        msisdn = msisdn.lstrip('0')
        msisdn = msisdn.lstrip('+')
        #check if the phone is the correct format
        if len(msisdn) == 9:
            try:
                #check if the user phone number is in the correct format
                match = PHONE_RE.match(msisdn)
                try:
                    #get the phone number
                    phone_number = match.group()
                    correct_format = True
                    phone_number = '27' + phone_number
                    return (phone_number, correct_format)
                except AttributeError:
                    logging.exception("invalid msisdn format.")
                    updated = False
                    return (phone, updated)
            except TypeError:
                logging.exception("The phone number must be in string format")
                phone_update = False
                return (phone, phone_update)
        #check if the phone is in internation format
        elif len(msisdn) == 11:
            try:
                #check if the user phone number is in the correct format
                match = MSISDNS_RE.match(msisdn)
                try:
                    #get the phone number
                    phone_number = match.group()
                    correct_format = True
                    return (phone_number, correct_format)
                except AttributeError:
                    logging.exception("invalid msisdn format.")
                    updated = False
                    return (phone, updated)
            except TypeError:
                logging.exception("The phone number must be in string format")
                phone_update = False
                return (phone, phone_update)
        else:
            logging.exception("The phone number must be 9 to 11 digits")
            phone_update = False
            return (phone, phone_update)

    def create_patient(self, patient_row, row_no, doc_name, start, until):
        #get the contents of the row
        file_no, file_format = self.check_file_no_format(patient_row['fileno'])
        phone, phone_format = self.check_msisdn_format(
            patient_row['phonenumber'])
        app_date = patient_row['appointmentdate1']
        app_status = patient_row['appointmentstatus1']
        #check if the file number is correct format
        if file_format:
            #visit id
            if row_no < 10:
                row_no = '0' + str(row_no)
                visit_id = str(row_no) + '-' + file_no
            else:
                visit_id = str(row_no) + '-' + file_no
            #get the owner
            # owner = self.get_or_create_owner('googledoc')
            #if phone format is valid create patient's msisdn
            if phone_format:
                #create or get phone number
                msisdn, msisdn_created = self.get_or_create_msisdn(phone)
            #check if the patient field are valid formats
            if self.owner and msisdn and file_no:
                #try to create a patient with the contents of the worksheet row
                try:
                    #create a new patient
                    new_patient = Patient(te_id=file_no,
                                          active_msisdn=msisdn,
                                          owner=self.owner)
                    #save to database
                    new_patient.save()
                    patient_created = True
                    # add msisdn to list of msisdns
                    new_patient.msisdns.add(msisdn)
                    #get or create a clinic
                    clinic = self.get_or_create_clinic(doc_name)
                    enrolled = self.set_cache_enrollement_status(
                        doc_name, file_no, start, until)
                    #check if the enrollment check was cached
                    enrolled = cache.get(file_no)
                    #convert string enroment to a choice key used in database
                    status = self.update_needed(app_status)
                    #if patient is enrolled create a visit instanceprint
                    if enrolled and patient_created and clinic:
                        #call method to create a visit for the new patient
                        visit, visit_created = self.create_visit(
                            visit_id, new_patient, app_date, status, clinic)
                        if visit_created:
                            #indicate that patient and visit was created
                            created = True
                            logging.debug("Created patient's visit")
                            return created
                    elif not enrolled:
                        #the patient was created and erollment status is false
                        return patient_created
                #catch relational integrity error
                except IntegrityError:
                    logging.exception("Failed to create patient invalid field")
                    patient_created = False
                    return patient_created
        #if any of the patient data was incorrect dont create patient
        else:
            logging.debug("Error creating patient with invalid file number")
            #indicate that patient could not be created becuase of the error
            created = False
            return created

    def create_visit(self, visit_id, new_patient, app_date, status, clinic):
        #use spreadsheet data to create visit
        try:
            #create a the visit
            new_visit = Visit(te_visit_id=visit_id,
                              patient=new_patient,
                              date=app_date,
                              status=status,
                              clinic=clinic)
            new_visit.save()
            logging.debug("Created the visit %s for new patien" % visit_id)
            visit_created = True
            visit = new_visit
        except IntegrityError:
            logging.exception("Failed to create visit %s." % visit_id)
            visit_created = False
            visit = None
        return (visit, visit_created)

    def get_or_create_clinic(self, doc_name):
        #get or create a clinic with the name of the spreadsheet
        clinic, created = Clinic.objects.get_or_create(name=doc_name,
                                                       user=self.owner)
        return clinic

    def get_or_create_owner(self, name):
        #get the user or create one
        owner, owner_created = User.objects.get_or_create(username=name)
        return owner

    def get_or_create_msisdn(self, phone):
        #create a msisdn
        msisdn, msisdn_created = MSISDN.objects.get_or_create(msisdn=phone)
        return (msisdn, msisdn_created)

    def update_msisdn(self, msisdn, curr_patient):
        '''
        @rguments:
        msisdn: the phone number to be updated
        curr_patient: is the patient who's phone number is being updated.

        The method checks if the phone number is in the correct format.
        Correct format for msisdn is: 123456789 / 27123456789
        If format is correct retrive it from database if not found
        create one and add it to the patient msisdns field.

        stores phone number in 27123456789 in the database

        @returns:
        created: Indicate whether an phone number update occurred
        '''
        phone, phone_format = self.check_msisdn_format(msisdn)
        if phone_format is True:
            #update the patient phone number
            phone, created = MSISDN.objects.get_or_create(msisdn=msisdn)
            if created:
                #check if the phone number is not on the list of MSISDN add it
                if phone not in curr_patient.msisdns.all():
                    curr_patient.msisdns.add(phone)
                    #set the patient's active msisdn to recently added msisdn
                    curr_patient.active_msisdn = phone
                logging.debug('Phone number update for patient: %s' %
                              curr_patient)
                return (phone.msisdn, created)
            elif not created:
                logging.debug('Phone number has not changed for patient: %s' %
                              curr_patient)
                return (phone.msisdn, created)
        # if the msisdn does not have correct phone number format log error
        else:
            logging.exception('invalid phone number format for patient: %s' %
                              curr_patient)
            phone_update = False
            return (msisdn, phone_update)

    def update_needed(self, status):
        """
        @rguments:
        status: appointment status.

        Converts the appointment status
        variable to the length of the
        one stored on the database.

        @returns:
        database_status: appointment status a letter.
        """

        if status == 'Missed':
            return 'm'
        elif status == 'Rescheduled':
            return 'r'
        elif status == 'Attended':
            return 'a'
        elif status == 'Scheduled':
            return 's'

    def update_appointment_status(self, app_status, new_patient, app_date,
                                  visit_id, clinic_name):
        """
        @rgument:
        app_status: appointment status.
        app_date: appointment date.
        visit_id: database unique identifier for the patient

        The method begins by checking if the patient has made an appointment.
        If apointment was made check that the date has not changed.
        If the date in worksheet is more than that in the database
        then the appointment has been rescheduled if it is equal
        to it check if the user has attended a re/scheduled appointment,
        if it was missed modify to missed.

        Use logging for error reports and debugging.

        @returns:
        updated: indicates whether the appointment status was updated.
        """
        #converts a string enroment to a choice key used in database
        status = self.update_needed(app_status)
        try:
            #get patient's visit instance
            curr_visit = Visit.objects.get(te_visit_id=visit_id)
        except Visit.DoesNotExist:
            #log error in import log
            logging.debug("Creating a new visit for patient")
            if type(clinic_name) == str:
                clinic = self.get_or_create_clinic(clinic_name)
            else:
                clinic = clinic_name
            curr_visit, visit_created = self.create_visit(
                visit_id, new_patient, app_date, status, clinic)
            #check if the visit was created
            if not visit_created:
                return status

        #stores variable used to check if appointment updates are needed
        progress = self.update_needed(app_status)
        #dont update if the status has not changed
        if progress == curr_visit.status:
            logging.debug("The appointment status does not require an update")
            return status

        #if visit exist update to current status
        if curr_visit:
            #if appointment date is coming or is now update status
            if curr_visit.date >= app_date:
                #check if the user has attended the scheduled appointment
                if app_status == 'Attended':
                    #check if it was scheduled or rescheduled
                    if curr_visit.status == 's' or curr_visit.status == 'r':
                        #if true change to attended
                        try:
                            #save new appointment status
                            curr_visit.status = 'a'
                            curr_visit.save()
                            logging.debug('Patient %s attended appointment' %
                                          visit_id)
                            return curr_visit.status
                        #except save fial
                        except IntegrityError:
                            logging.exception("Appointment failed to update")
                            return curr_visit.status

                #if the user has missed a scheduled or rescheduled appointment
                if app_status == 'Missed':
                    if curr_visit.status == 's' or curr_visit.status == 'r':
                        try:
                            curr_visit.status = 'm'
                            curr_visit.save()
                            logging.debug('Patient missed appointment %s' %
                                          visit_id)
                            return curr_visit.status
                        except:
                            logging.exception("Appointment failed to update")
                            return curr_visit.status

            #check if the patient appointment date has passed
            elif curr_visit.date < app_date:
                #check if the patient has rescheduled
                if app_status == 'Rescheduled' and curr_visit.status == 's':
                    curr_visit.status = 'r'
                    try:
                        #change date to be the new rescheduled appointment date
                        curr_visit.date = app_date
                        curr_visit.save()
                        logging.debug('Patient %s rescheduled' % visit_id)
                        return curr_visit.status
                    except:
                        logging.exception("Appointment failed to update")
                        return curr_visit.status