def __init__(self, work_date):
     self.work_date = work_date
     self.work_day = self.is_valid()
     self.next_valid_day = None
     self.db = DBCommands()
     self.ii = InsertItem()
     self.db.connect()
class DTree(object):

    def __init__(self, work_day, path):
        self.work_day = work_day
        self.dtree = load(filename='./machine_learning/glasses_dtree.joblib')
        self.db = DBCommands()
        self.db.connect()

    def __del__(self):
        self.db.conn.close()

    def pull_patient_id(self):
        patient_list = []
        patients = self.db.view_free(
            f"SELECT patient FROM schedule WHERE appt_date = '{self.work_day}' ORDER BY appt_time", slow=False)
        for patient in patients:
            patient_list.append(patient[0])  # Patients in order of appointment times
        return patient_list

    def pull_patient_data(self, patient):
        data = self.db.view_free(f"SELECT * FROM glasses_data WHERE id = {patient}", slow=False)
        return data

    def process_data(self, data):
        if len(data) == 0:
            return 'New'
        else:
            columns = ['id', 'patient_name', 'address', 'insurance', 'avg_dollar', 'age', 'gender', 'lat', 'lon',
                       'first_purchase', 'last_purchase', 'purchase_time', 'total_paid', 'used_ins', 'product_id',
                       'price', 'appt_type', 'buying_pattern']  # Getting column titles ready
            df = pd.DataFrame(data, columns=columns)
            df.drop_duplicates(inplace=True)
            df.drop(columns=['address', 'first_purchase', 'last_purchase', 'patient_name', 'purchase_time', 'id', 'lat',
                             'lon', 'buying_pattern'], axis=1, inplace=True)  # Cleaning data

            # Getting categorical data, including missing columns then dropping first.
            gender = pd.get_dummies(df['gender']).reindex(columns=['female', 'male'], fill_value=0)
            exam_type = pd.get_dummies(df['appt_type']).reindex(columns=['Contacts', 'Glasses', 'Health'],
                                                                fill_value=0)
            insurance = pd.get_dummies(df['insurance']).reindex(columns=['Good', 'Standard', 'Poor', 'None'],
                                                                fill_value=0)
            gender.drop(columns=['female'], axis=1, inplace=True)
            exam_type.drop(columns=['Contacts'], axis=1, inplace=True)
            insurance.drop(columns=['Good'], axis=1, inplace=True)
            df.drop(columns=['gender', 'appt_type', 'insurance', 'used_ins'], axis=1, inplace=True)

            df2 = pd.concat([df, gender, exam_type, insurance], join='inner', axis=1)

            predictions = self.dtree.predict(df2)
            try:
                return mode(predictions)
            except StatisticsError:
                return predictions[0]

    def predict_pattern(self):
        pattern_list = []
        for i in self.pull_patient_id():
            pattern_list.append(self.process_data(self.pull_patient_data(i)))
        return pattern_list
def pull_data():
    db = DBCommands()
    columns = [
        'ID', 'Name', 'Address', 'Ins', 'Average Dollar', 'Age', 'Gender',
        'Latitude', 'Longitude', 'First Purchase', 'Last Purchase'
    ]
    data = db.view_free('SELECT * FROM patients WHERE avg_dollar IS NOT NULL')
    df = pd.DataFrame(data, columns=columns)
    return df
 def __init__(self, patient):
     self.patient = patient
     self.db = DBCommands()
     self.db.connect()
     self.purchases = [
     ]  # This will be what is used for sales. Empty list will mean no purchase.
     self.patient_demographics = None  # Pulled from "patients" table
     self.patient_auto = None  # Pulled from "auto_patient" table
     self.patient_history = None  # Pulled from "sale_item" table. Will need to have dates from "sale" table also.
     self.patient_history_dates = []  # Date of each sale
     self.patient_insurance = (0, 'None'
                               )  # Separated to allow easier indexing.
     self.product_list = None
 def __init__(self,
              patient_id,
              days_out,
              current_date=None,
              appt_type='Glasses'):
     self.patient_id = patient_id
     self.current_date = current_date if current_date else date.today()
     self.exam_date = self.days_to_date(
         days_out)  # Date desired for appointment ( 02/05/2019 )
     self.initial_exam_date = self.days_to_date(days_out)
     self.db = DBCommands()
     self.db.connect()  # Connect this class to DB
     self.days = plogic.working_days
     self.time = plogic.business_hours
     self.appt_time = plogic.appt_slot
     self.appt_type = appt_type
class ProductPurchase(object):
    def __init__(self, patient):
        self.patient = patient
        self.db = DBCommands()
        self.db.connect()
        self.purchases = [
        ]  # This will be what is used for sales. Empty list will mean no purchase.
        self.patient_demographics = None  # Pulled from "patients" table
        self.patient_auto = None  # Pulled from "auto_patient" table
        self.patient_history = None  # Pulled from "sale_item" table. Will need to have dates from "sale" table also.
        self.patient_history_dates = []  # Date of each sale
        self.patient_insurance = (0, 'None'
                                  )  # Separated to allow easier indexing.
        self.product_list = None

    def __del__(self):
        self.db.conn.close()  # Nothing will be recorded in DB

    def pull_product_list(self):
        i = self.db.view('products', field='id, product', slow=False)
        self.product_list = i

    def pull_patient_demographics(self):
        i = self.db.view('patients',
                         field='id, insurance, age, gender',
                         conditional=('id', self.patient),
                         slow=False)
        self.patient_demographics = i[0]

    def pull_patient_auto(self):
        i = self.db.view('auto_patient',
                         conditional=('patient_id', self.patient),
                         slow=False)
        self.patient_auto = i[0][1:]

    def pull_patient_history(self):
        i = self.db.view_free(
            f"SELECT sale_item.product_id, sale.purchase_time FROM sale_item INNER JOIN "
            f"sale ON sale_item.sale_id = sale.id WHERE sale.patient = {self.patient}",
            slow=False)
        if len(i) > 0:  # If there is a history
            self.patient_history = {}  # Change to dictionary
            dates = set()
            for item in i:  # Pull dates from data
                dates.add(item[1])
            self.patient_history_dates = list(dates)
            self.patient_history_dates.sort()
            for day in self.patient_history_dates:
                items = []
                sales = [x for x in i if x[1] == day
                         ]  # Sort by dates to prevent massive list iterations
                for item in sales:  # Pull sales data
                    items.append(item[0])
                    self.patient_history[day] = items

    def pull_patient_data(
        self
    ):  # Created to allow multiple ways to pull data in future if needed.
        self.pull_patient_demographics()
        self.pull_patient_auto()
        self.pull_patient_history()
        self.set_insurance()

    def run_sale(self):
        self.pull_patient_data()
        self.pull_product_list()
        self.purchases = []  # Reset purchase list to prevent doubles
        self.purchase_exam()
        self.purchase_contacts()
        count = self.purchase_glasses()
        if count > 0:
            self.purchases += self.glasses_type(count)

    def set_insurance(self):
        if self.patient_demographics:
            found = False
            patient_insurance = self.patient_demographics[1]
            for i in enumerate(insurance):
                if patient_insurance == i[1]:
                    self.patient_insurance = i
                    found = True
            if not found:
                self.patient_insurance = (0, 'None')

    def purchase_exam(self):  # Used to populate exam ids, needs to run first.
        if self.patient_auto[2] == 'Health':
            self.purchases += [8, 5]

        elif self.patient_auto[2] == 'Contacts':
            if self.patient_history:
                found = False
                for i in reversed(self.patient_history_dates
                                  ):  # Dictionary Date: [purchases]
                    if 6 in self.patient_history[i]:
                        self.purchases += [1, 5, 6]
                        found = True
                        break  # Stop after first found
                    elif 7 in self.patient_history[i]:
                        self.purchases += [1, 5, 7]
                        found = True
                        break
                if not found:
                    self.first_cl_exam()
            else:
                self.first_cl_exam()

        else:  # Glasses purchase
            self.purchases += [1, 5]

        # Check for optimap purchase
        count = 0
        if self.patient_history:
            for i in reversed(self.patient_history_dates):
                if count > 3 or count < -3:
                    break
                if 9 in self.patient_history[i]:
                    count += 1
                else:
                    count -= 1
        chance = random.randint(1, 50) + (loyal_optimap * count) + optimap[int(
            self.patient_auto[1])]
        if chance >= 30:
            self.purchases += [9]

    def first_cl_exam(self):
        chance = random.randint(1, 100)
        exam_type = cl_exam_type
        if self.patient_demographics[2] >= 40:
            age = self.patient_demographics[2] - 30
            special_fit = int(age / 10) * cl_age_factor
            exam_type -= special_fit
        if chance >= cl_exam_type:
            self.purchases += [1, 5, 7]
        else:
            self.purchases += [1, 5, 6]

    def pick_contacts(self):
        contact_type = None
        chance = random.randint(1, 100)
        for i in type_contacts:
            if chance <= type_contacts[i][int(self.patient_auto[1])]:
                contact_type = i
                break
            else:
                chance -= type_contacts[i][int(self.patient_auto[1])]
        return contact_type

    def purchase_contacts(self):
        if self.patient_auto[2] != 'Contacts':
            return None
        contact_type = None
        if self.patient_history:
            contacts = {26, 27, 28}
            count = 0
            for i in reversed(self.patient_history_dates):
                if contact_type is None:
                    contact_type = contacts.intersection(
                        self.patient_history[i])
                    pass
                if contacts.intersection(
                        self.patient_history[i]) == contact_type:
                    count += 1
                else:
                    break  # Stop looking after patient switched type
                if count >= abs(switch_contacts[1] / switch_contacts[0]):
                    break  # Stop looking after 0 % switch chance
            switch_chance = switch_contacts[1] - (switch_contacts[0] * count)
            if len(contact_type) > 0:
                contact_type = self.product_id(list(contact_type)[0],
                                               to_id=False)  # Pull CL name
            else:
                contact_type = self.pick_contacts()
            if switch_chance > 0:
                chance = random.randint(1, 100)
                if chance < switch_chance:
                    contact_type = self.pick_contacts()

        else:
            contact_type = self.pick_contacts()
        if contact_type is None:
            raise ValueError  # Should be a string at this point
        else:
            ins_index = 1 if self.patient_insurance[0] > 0 else 0
            amount_contacts = None
            chance = random.randint(1, 100)
            for i in buy_contacts:
                amount = buy_contacts[i][int(self.patient_auto[1])][ins_index]
                if chance > amount:
                    chance -= amount
                else:
                    try:
                        amount_contacts = int(contacts_amount[i][contact_type])
                    except TypeError:
                        print(contacts_amount[i])
                    break
            if amount_contacts is not None:
                for i in self.product_list:
                    if contact_type == i[1]:
                        contact_type = i[
                            0]  # Change to id number for quick sale
                self.purchases += [contact_type] * amount_contacts

    def purchase_glasses(self):
        purchase_chance = buy_glasses[int(
            self.patient_auto[1])][self.patient_insurance[0]]  # Initial value
        bottom, top = random_glasses[int(self.patient_auto[1])]
        purchase_chance += random.randint(bottom, top)
        if self.patient_history:
            frames = {10, 11, 12}
            last_purchase = self.patient_history[
                self.patient_history_dates[-1]]
            purchased_before = bool(frames.intersection(last_purchase))
            if not purchased_before:
                if self.patient_insurance[0] > 0:
                    purchase_chance += skip_glasses[int(
                        self.patient_auto[1])][1]
                else:
                    purchase_chance += skip_glasses[int(
                        self.patient_auto[1])][0]
        if self.patient_auto[2] != 'Glasses':
            purchase_chance += non_glasses
        purchase_count = 0
        while purchase_chance > 0:
            purchase = random.randint(1, 100)
            if purchase < purchase_chance:
                purchase_count += 1  # Purchased glasses
            purchase_chance -= glasses_threshold
        return purchase_count

    def glasses_type(self,
                     count):  # Puts together all the items on the glasses
        master_purchases = []
        for x in range(count):
            purchase_list = []
            purchase_list.append(self.product_id(self.pick_frame()))
            purchase_list.append(self.product_id(self.pick_lens_type()))
            purchase_list.append(self.product_id(self.pick_lens_material()))
            ar_purchased = self.pick_ar_type()
            if ar_purchased and ar_purchased != 'None':
                purchase_list.append(self.product_id(ar_purchased))
            if x == 1:  # Second pair
                polar_purchased = self.pick_polarized(multi=True)
            else:
                polar_purchased = self.pick_polarized()
            if polar_purchased:
                purchase_list.append(polar_purchased)
            if 25 not in purchase_list:
                trans_purchase = self.pick_transitions()
                if trans_purchase:
                    purchase_list.append(trans_purchase)
            master_purchases += purchase_list
        return master_purchases

    def pick_frame(self):
        chance = random.randint(
            1, 100
        ) + frame_gender if self.patient_demographics[3] == 'female' else 0
        for i in frame_type:
            if chance > frame_type[i][int(
                    self.patient_auto[1])][self.patient_insurance[0]]:
                chance -= frame_type[i][int(
                    self.patient_auto[1])][self.patient_insurance[0]]
            else:
                return i  # String of frame

    def pick_lens_type(self):  # Choose SV, BF, Prog
        if self.patient_demographics[2] < 40:
            return 'SV Lenses'
        else:
            pass
            patient_lens = None
            if self.patient_history:
                lenses = {13, 14, 15, 16, 17}
                count = 0
                for i in reversed(self.patient_history_dates):
                    if patient_lens is None:
                        patient_lens = lenses.intersection(
                            self.patient_history[i])
                        pass
                    else:
                        if patient_lens == lenses.intersection(
                                self.patient_history[i]):
                            count += 1
                            if count >= 3:
                                break
                        else:
                            chance = random.randint(1, 100)
                            if chance > switch_lens_type - (count * 5):
                                break
                            else:
                                patient_lens = None  # Jump to lens selection
            if patient_lens is None or len(patient_lens) == 0:
                chance = random.randint(1, 100)
                for i in lens_type:
                    if chance > lens_type[i]:
                        chance -= lens_type[i]
                    else:
                        if i == 'Progressives':
                            chance = random.randint(1, 100)
                            for x in progressive_type:
                                if chance > progressive_type[x][int(
                                        self.patient_auto[1])][
                                            self.patient_insurance[0]]:
                                    chance -= progressive_type[x][int(
                                        self.patient_auto[1])][
                                            self.patient_insurance[0]]
                                else:
                                    i = x
                                    return i
                        return i  # Returned as string, will have a separate function to change to id.
            return self.product_id(list(patient_lens)[0], to_id=False)

    def pick_lens_material(self):
        if self.patient_demographics[2] < 40:
            return 'Polycarbonate'
        else:
            chance = random.randint(1, 100)
            lenses = None
            if self.patient_history:
                types = {20: "Hi-Index", 19: "Polycarbonate", 18: "Plastic"}
                lens_types = {18, 19, 20}
                for i in reversed(self.patient_history_dates):
                    lenses = lens_types.intersection(self.patient_history[i])
                    if len(lenses) > 1:
                        lenses = types[list(lenses)[0]]
                        break
                    else:
                        lenses = None
            chance += lens_insurance[
                self.patient_insurance[0]] + lens_buy_pattern[int(
                    self.patient_auto[1])]
            try:
                rx = lens_rx[self.patient_auto[6]]
            except IndexError:  # Outside the high end of the Rx index
                rx = lens_rx[-1]
            chance += rx
            for i in lens_material:
                if lenses == i:
                    chance += loyal_lens_material
                if chance >= lens_material[i]:
                    return i
            return "Plastic"  # 'Else' choice

    def pick_ar_type(self):
        chance = random.randint(1, 50)
        chance += ar_insurance[self.patient_insurance[0]] + ar_buy_pattern[int(
            self.patient_auto[1])]
        ar = None
        if self.patient_history:
            types = {21: 'Standard AR', 22: 'Good AR', 23: 'Great AR'}
            ar_types = {21, 22, 23}
            for i in reversed(self.patient_history_dates):
                ar = ar_types.intersection(self.patient_history[i])
                if ar:
                    ar = list(ar)[0]
                    ar = types[ar]
                    break
        for i in ar_type:
            if i == ar:
                chance += 20
            if chance >= ar_type[i]:
                return i
        return None

    def pick_transitions(self):
        chance = random.randint(
            1, 50) + trans_age if self.patient_demographics[2] < 20 else 0
        chance += trans_insurance[
            self.patient_insurance[0]] + trans_buy_pattern[int(
                self.patient_auto[1])]
        if self.patient_history and len(self.patient_history) > 1:
            count = 0
            had_it = None
            glasses = {10, 11, 12}
            for i in reversed(self.patient_history_dates):
                purchased = glasses.intersection(self.patient_history[i])
                if purchased:  # Did they buy glasses last time?
                    if had_it is None:
                        if 24 in purchased:
                            had_it = True
                        elif 25 in purchased:  # Polarized doesn't count
                            pass
                        else:
                            count += 1
                    elif count == 1:
                        if 24 in purchased:
                            chance = 0 - trans_loyal  # Did not buy it after having it, must hate it.
                            break
                        elif 25 in purchased:
                            pass
                        else:
                            count += 1
                    elif had_it:
                        if 24 in purchased:
                            chance = 0 + trans_loyal  # Bought it twice in a row, must like it
                            break
                        elif 25 in purchased:
                            pass
                        else:
                            break
        if chance > trans_buy:
            return 24  # Purchased transitions

    def pick_polarized(self, multi=False):
        chance = random.randint(1, 50)
        chance += polar_insurance[
            self.patient_insurance[0]] + polar_buy_pattern[int(
                self.patient_auto[1])]
        if multi:
            second_pair = 0 + second_pair_sun
        else:
            second_pair = 0 - (second_pair_sun * 2)
        if self.patient_history and not multi:
            for i in reversed(self.patient_history_dates):
                glasses = {10, 11, 12}
                purchased = glasses.intersection(self.patient_history[i])
                if purchased:
                    if 25 not in self.patient_history[i]:
                        second_pair = 0 + second_pair_sun  # Did not purchase sun last time.
                        break
        if chance + second_pair > polar_buy:
            return 25

    def product_id(self, product, to_id=True):  # Turns str to id or id to str
        if to_id:
            if type(product) == str:
                for i in self.product_list:
                    if product in i:
                        return i[0]
        if not to_id:
            if type(product) == int:
                for i in self.product_list:
                    if product in i:
                        return i[1]
Esempio n. 7
0
def setup_db():
    ii = DBCommands()
    ii.connect()
    yield ii
    ii.conn.rollback()
    ii.conn.close()
 def __init__(self, work_day, path):
     self.work_day = work_day
     self.dtree = load(filename='./machine_learning/glasses_dtree.joblib')
     self.db = DBCommands()
     self.db.connect()
Esempio n. 9
0
 def __init__(self):
     self.db = DBCommands()
Esempio n. 10
0
class InsertItem(object):   # This would connect to the front end to allow DB editing
    def __init__(self):
        self.db = DBCommands()

    def _id_lookup(self, table, column, item, slow=True):  # Returns id of item
        id_key = None
        try:
            id_key = self.db.view(table, conditional=(column, item), field="id", slow=slow)
        except Exception:
            self.db.rollback()
        if id_key is None:
            print(f'DB Error, could not find id of {item}')
        else:
            return id_key[0][0]  # Should never be none: Cannot sale to unknown, nor sale unknown item.

    def patient_insert(self, name, address='', gender='', age='', insurance='None'):  # Needs a name
        try:
            self.db.insert(['patient', ('patient_name', name), ('address', address), ('gender', gender),
                            ('age', age), ('insurance', insurance)])
        except Exception:   # Look at exceptions that could hinder insert.
            self.db.rollback()

    def insert_sale(self, patient, time=None, insurance='None', purchase_items=(('Test', 0))):
        # Slow and terrible, quick_sale will be the main input function.
        # Expects a nested list with [item, price] as each item.
        total_sum = 0
        sale_time = time if time else dt.datetime.now()  # Insert current time if none supplied
        patient_id = self._id_lookup('patients', 'patient_name', patient)
        items = []
        for item in purchase_items:
            item_id = self._id_lookup('products', 'product', item[0])
            total_sum += int(item[1])
            items.append([item_id, item[1]])    # Change item name to item id
        # Create sale (patient, purchase_time, total_paid, used_ins)
        self.db.insert(['sale', ('patient', patient_id), ('purchase_time', sale_time),
                        ('total_paid', total_sum), ('used_ins', insurance)])

        sale_id = self.db.view(f"sale WHERE purchase_time = '{sale_time}' AND patient = {patient_id}", field="id")

        for item in items:  # Creating sale_item table
            self.db.insert(['sale_item', ('product_id', item[0]), ('sale_id', sale_id[0][0]), ('price', item[1])])

        self.db.update_avg_dollar(patient_id)
        self.db.update_timestamp(patient, sale_time)
        print(f"Patient {patient} recorded.")

    def quick_sale(self, patient_id, time, insurance, item_ids):    # Used for quickly inputting sales for testing
        total_sum = 0
        sale_time = time if time else dt.datetime.now()  # Insert current time if none supplied
        items = []

        for item_id in item_ids:
            items.append(self.db.view('products', field="id, cost", conditional=("id", item_id), slow=False)[0])
            total_sum += int(items[0][1])

        self.db.insert(['sale', ('patient', patient_id), ('purchase_time', sale_time),
                        ('total_paid', total_sum), ('used_ins', insurance)], slow=False)

        sale_id = self.db.view(f"sale WHERE purchase_time = '{sale_time}' AND patient = {patient_id}",
                             field="id", slow=False)

        for item in items:  # Creating sale_item table
            self.db.insert(['sale_item', ('product_id', item[0]), ('sale_id', sale_id[0][0]),
                            ('price', item[1])], slow=False)

        self.db.update_avg_dollar(patient_id,slow=False)
        self.db.update_timestamp(patient_id, sale_time, slow=False)
 def __init__(self, number, current_day=None):
     self.number = number
     self.current_day = current_day if current_day else date.today()
     self.db = DBCommands()
     self.db.connect()
class NewPatient(object):
    """
    Takes new patients and inserts them into the schedule. If patient does not have proper info,
    (Buying pattern, exam type) then it will fill those in. If no new patient exists, it will create one from scratch.
    Logic in a separate file for easier editing.
    """
    def __init__(self, number, current_day=None):
        self.number = number
        self.current_day = current_day if current_day else date.today()
        self.db = DBCommands()
        self.db.connect()

    def __del__(self):
        self.db.commit_close()

    def patient_selector(self):
        # self.db.connect()
        new_patients = self.db.view_free(
            "SELECT patients.id, patients.age FROM patients WHERE NOT "
            "EXISTS(SELECT patient FROM schedule WHERE patients.id=schedule.patient)",
            slow=False)  # Pull all patients that have never been seen.

        while len(new_patients) < self.number:
            self.create_patient()
            print("Not enough patients to add to scheduler.")
            break

        selected_patients = random.sample(new_patients, self.number)
        for patient in selected_patients:
            patient_id = patient[0]
            age = patient[1]
            info = self.db.view('auto_patient',
                                field="buying_pattern, exam_type",
                                conditional=["patient_id", patient_id],
                                slow=False)
            # Check if patient exists in auto_table
            if len(info) == 0:  # If patient not in auto patient
                buying_pattern = plogic.create_buying_pattern()
                exam_type = plogic.create_exam_type(age)
                rx_str = plogic.create_rx_strength()
                self.db.insert([
                    "auto_patient", ("patient_id", patient_id),
                    ("buying_pattern", buying_pattern),
                    ("exam_type", exam_type), ("rx_strength", rx_str)
                ],
                               slow=False)
                insurance = plogic.create_insurance_type()
                self.db.update(
                    ['patients', ('insurance', insurance), ('id', patient_id)],
                    slow=False)
                days_out = random.randint(7,
                                          14)  # Set date for week to two weeks
                sch = Scheduler(patient_id, days_out, self.current_day,
                                exam_type)
                sch.schedule_patient()  # Put patient on schedule
            else:
                exam_type = info[0][1]
                print(exam_type)
                days_out = random.randint(7,
                                          14)  # Set date for week to two weeks
                sch = Scheduler(patient_id, days_out, self.current_day,
                                exam_type)
                sch.schedule_patient()  # Put patient on schedule
        # self.db.commit_close()

    def create_patient(self):
        # Create patient from scratch, calling all other info. ( Maybe turn into its own class / file )
        # Eventually this will also auto make buying pattern and exam type as well. ( Full patient creation )
        pass
class ProcessWorkDay(object):
    """
    Does the work of running through each patient on that days schedule, deciding purchases, events and rescheduling.
    """
    def __init__(self, work_date):
        self.work_date = work_date
        self.work_day = self.is_valid()
        self.next_valid_day = None
        self.db = DBCommands()
        self.ii = InsertItem()
        self.db.connect()

    def __del__(self):
        self.db.commit_close()

    def is_valid(self):
        if self.work_date.weekday() in plogic.working_days:
            return True  # Used for sales that are outside of working days, such as medical or glasses broke.
        else:
            return False

    def process_day(self):
        if self.work_day:
            self.ii.db.connect()
            patients = self.db.view_schedule(self.work_date)
            patient_list = []
            for patient in patients:  # Seems messy, maybe easier way to do this?
                patient_list.append(patient[1])
            for patient in patient_list:
                data = self.db.view('patients',
                                    conditional=('id', patient),
                                    field='insurance',
                                    slow=False)[0]
                insurance = data[0]
                auto_patient = self.db.view('auto_patient',
                                            ('patient_id', patient),
                                            slow=False)
                if len(auto_patient) == 0:
                    print(f"Patient {patient} not in auto_patient."
                          )  # Trying to catch a bug
                else:
                    auto_product = ProductPurchase(
                        auto_patient[0][1])  # Get id of patient
                    auto_product.run_sale()
                    purchase_list = auto_product.purchases
                    if len(purchase_list) > 0:
                        self.ii.quick_sale(patient, self.work_date, insurance,
                                           purchase_list)
                        last_purchases = plogic.last_purchase(purchase_list)
                        for item in last_purchases:  # Something here is not working, insert test?
                            self.db.cmd_free(
                                f"UPDATE auto_patient SET {item} = '{self.work_date}' "
                                f"WHERE patient_id = {patient}",
                                slow=False)

            self.db.conn.commit()  # Separate connections
            self.ii.db.commit_close()

    def check_future_appointments(self):
        for day in plogic.dates:
            back_date = self.work_date - timedelta(day)
            previous_patients = self.db.view('auto_patient',
                                             conditional=('last_exam_date',
                                                          back_date),
                                             slow=False)
            for patient in previous_patients:
                _, patient_id, buying_pattern, exam_type, last_exam, last_glasses, last_contacts, rx_str = patient
                insurance = self.db.view('patients',
                                         field='insurance',
                                         conditional=('id', patient_id),
                                         slow=False)[0][0]
                has_insurance = False
                if insurance in plogic.insurances:
                    has_insurance = True
                schedule = plogic.schedule(int(buying_pattern), exam_type, day,
                                           has_insurance)
                if schedule:
                    days_out = random.randint(7, 14)
                    sch = Scheduler(patient_id, days_out, self.work_date)
                    sch.schedule_patient()

    def life_happens(self):
        # Use a separate file for logic
        # Go through patients, and see if any need special help
        # If so, run scheduler or sale based on need.
        # If not valid work day, then sale on next valid day ( automatically )
        # Record events ( Just for testing )
        pass

    def check_contacts(self):
        # Look through DB for all people who are overdue for CL's ( Look at last sale date, and CL type, times # boxes)
        # Check if exam is valid ( under a year )
        # if exam valid: Based on buying pattern, purchase new CL's
        # if exam not valid: Schedule new exam
        # Make sure date is valid
        pass

    def record_day(self):  # Turned off for the moment.
        self.db.connect()
        totals = self.db.view('sale',
                              conditional=('purchase_time', self.work_date),
                              slow=False)
        patients = len(totals)
        total_dollar = 0
        for person in totals:
            total_dollar += person[3]
        with open('Daily_totals.txt', 'a') as file:
            file.write(f"On {self.work_date} there was {patients} "
                       f"scheduled and we earned ${total_dollar/100}.\n")

    def run_day(self):  # Puts everything together in order.
        self.process_day()
        self.check_future_appointments()
class Scheduler(object):
    """
    Takes patient and will schedule them in an appropriate time slot.
    """
    def __init__(self,
                 patient_id,
                 days_out,
                 current_date=None,
                 appt_type='Glasses'):
        self.patient_id = patient_id
        self.current_date = current_date if current_date else date.today()
        self.exam_date = self.days_to_date(
            days_out)  # Date desired for appointment ( 02/05/2019 )
        self.initial_exam_date = self.days_to_date(days_out)
        self.db = DBCommands()
        self.db.connect()  # Connect this class to DB
        self.days = plogic.working_days
        self.time = plogic.business_hours
        self.appt_time = plogic.appt_slot
        self.appt_type = appt_type

    def __del__(self):
        self.db.commit_close()

    def schedule_patient(self):
        self.date_is_valid()
        appt_time = self.time_is_valid()
        try:
            self.db.insert([
                'schedule', ('patient', self.patient_id),
                ('appt_date', self.exam_date), ('appt_time', appt_time),
                ('appt_type', self.appt_type)
            ],
                           slow=False)
        except Exception:
            self.db.rollback()

    def days_to_date(self, days_out):
        return self.current_date + timedelta(days_out)

    def date_is_valid(self):
        valid = False
        while not valid:
            if self.exam_date.weekday(
            ) in self.days:  # If date is listed as open in self.days
                valid = True
            else:
                self.exam_date += timedelta(1)  # Advance forward one day

    def time_is_valid(self):
        valid = False
        while not valid:
            appointments = self.db.view_schedule(self.exam_date)
            if len(appointments) == 0:  # No appointments on that day.
                appt_time = [self.time[0], 0]  # Book earliest appointment
                return time(appt_time[0], appt_time[1])
            else:
                latest_hour = str(appointments[-1][2]).split(":")[0]
                latest_minute = str(appointments[-1][2]).split(":")[1]
                appt_time = [
                    int(latest_hour),
                    int(latest_minute) + self.appt_time
                ]  # Set next appointment
                if appt_time[1] >= 60:
                    appt_time[0] += 1  # Goto next hour.
                    appt_time[1] -= 60
                if appt_time[0] >= self.time[1]:  # Past work time
                    self.exam_date += timedelta(1)  # Try next day
                    self.date_is_valid()  # Make sure it's a business day
                else:
                    return time(appt_time[0], appt_time[1])
        # self.db.conn.close()
        # Check for earliest time available for date. ( 10 - 5 , in 30 minute intervals )
        # If full, add a day and run date_is_valid again.

    def date_difference(self):
        difference = str(self.exam_date - self.initial_exam_date)
        if "," in difference:
            return int(difference.split(" ")[0])  # Get difference in days
        else:
            return 0  # Same day appointment