def check_alignment(row):
    if row['outcome'] == 'perfect':
        return row['outcome']
    ot.print_center('================== \u001b[1mForward\u001b[0m ==================\n')
    forward = row['for_align_str'].replace('.','\u001b[41m.\u001b[0m')
    forward = re.sub(r'(?<=[A-Z])-(?=[A-Z])', '\u001b[41m-\u001b[0m',forward)
    print(forward)
    ot.print_center('================== \u001b[1mReverse\u001b[0m ==================\n')
    reverse = row['rev_align_str'].replace('.','\u001b[41m.\u001b[0m')
    reverse = re.sub(r'(?<=[A-Z])-(?=[A-Z])', '\u001b[41m-\u001b[0m',reverse)
    print(reverse)
    ot.print_center('{}: \u001b[31;1m\u001b[1m{}\u001b[0m'.format(row['part_id'],row['outcome']))
    print('Options: k:keep, c:check, p:perfect, m:mutation, b:bad_reads,f:cloning_failure')
    ans = ot.request_info('Please state the outcome: ',type='string',select_from=['c','k','p','m','b','f'])
    if ans == 'k':
        return row['outcome']
    elif ans == 'c':
        return 'CHECK'
    elif ans == 'p':
        return 'perfect'
    elif ans == 'm':
        return 'mutation'
    elif ans == 'b':
        return 'bad_reads'
    elif ans == 'f':
        return 'cloning_failure'
Exemple #2
0
def check_calibration(robot, pipette, container, height=20):
    if 's' in pipette.name or '200' in pipette.name:
        sub_location = container.wells(0)
    elif container.get_type() == 'point':
        sub_location = container
    elif 'tiprack' in str(container.get_type()):
        sub_location = container[0]
    else:
        sub_location = container.rows(0)
    pipette.move_to(sub_location.top(z=height), strategy='arc')
    ans = ot.request_info('Does it line up? (y/n): ', type='string')
    if ans != 'n':
        if 'tiprack' in str(container.get_type()):
            pipette.move_to(sub_location)
        else:
            pipette.move_to(sub_location.top())
        ans = ot.request_info('Still aligned? (y/n): ', type='string')
        if ans != 'n':
            pipette.move_to(sub_location.bottom())
        else:
            print('Drive OT from here')
    else:
        ans = ot.request_info('Need to rehome? (y/n): ', type='string')
        if ans != 'n':
            robot.home()
            ans = ot.request_info('Try a little higher? (y/n): ',
                                  type='string')
            if ans != 'n':
                return check_calibration(robot,
                                         pipette,
                                         container,
                                         height=height + 20)
    print("Adjust {} calibration".format(container.get_type()))
    move_ot(pipette)
    if 'tiprack' in str(container.get_type()):
        print("Adjust tiprack calibration")
        check_tips(pipette, sub_location)
    else:
        pipette.calibrate_position(
            (container, container[0].from_center(x=0,
                                                 y=0,
                                                 z=-1,
                                                 reference=container)))

    return sub_location
Exemple #3
0
def check_tips(pipette, sub_location):
    pipette.drop_tip(location=sub_location, home_after=False)
    pipette.pick_up_tip(location=sub_location)
    pipette.move_to(sub_location.top(z=30))
    ans = ot.request_info('Are tips sufficiently attached? (y/n): ',
                          type='string')
    if ans == 'n':
        pipette.drop_tip(location=sub_location, home_after=False)
        pipette.move_to(sub_location.bottom())
        move_ot(pipette)
        return check_tips(pipette, sub_location)
    else:
        return
 def exit_handler():
     print('Choose one of the following options:')
     print('1-Save successful assembly\n2-Restore plan\n3-Abandon plan')
     ans = ot.request_info('Select what to do: ',
                           type='int',
                           select_from=[1, 2, 3])
     if ans == 1:
         ot.print_center('...Assembly is complete...')
     elif ans == 2:
         target_build.status = 'planning'
         ot.print_center('...Restoring the build plan...')
         for part in session.query(Part).filter(
                 Part.part_id.in_(build_plan.part_id.unique().tolist())):
             part.change_status('planning')
     elif ans == 3:
         target_build.status = 'abandoned'
         ot.print_center('...Unstaging all parts in build plan...')
         for part in session.query(Part).filter(
                 Part.part_id.in_(build_plan.part_id.unique().tolist())):
             part.change_status('received')
     session.commit()
import pandas as pd
from config import *
from db_config import *
import ot_functions as ot

session, engine = connect_db()

query_parts = 'SELECT * FROM parts'

data = pd.read_sql_query(query_parts, con=engine)

print('Currently there are {} parts in the database'.format(len(data)))

if len(data) == 0:
    prefix = ot.request_info(
        'Enter the desired prefix for the unique id numbers (i.e. "initials"_)'
    )
else:
    prefix = data.part_id.tolist()[0].split('_')[0] + '_'

start_id = len(data) + 1

files = []
for i, file in enumerate(glob.glob('{}/src/*.csv'.format(BASE_PATH))):
    print('{}: {}'.format(i, file))
    files.append(file)

ans = ot.request_info('Which file would you like to upload: ',
                      type='int',
                      select_from=[num for num in range(len(files))])
target_file = files[ans]
def create_build_plans(session,engine,num_parts,frag_nums,enzyme='BbsI',max_rxns=96):
    # Take in the 'run' argument from the command line
    parser = argparse.ArgumentParser(description="Resuspend a plate of DNA on an Opentrons OT-1 robot.")
    parser.add_argument('-r', '--run', required=False, action="store_true", help="Send commands to the robot and print command output.")
    args = parser.parse_args()

    ot.print_center("============ Creating Plan ============")
    num_builds = math.ceil(num_parts / 96)
    print(num_builds)
    used_plates = []

    if num_parts < max_rxns:
        print('Generating a partial build')
        max_rxns = num_parts

    previous_plans = [build.build_name for build in session.query(Build).order_by(Build.build_name).filter(Build.status == 'planning')]
    if len(previous_plans) > 0:
        print("Are you sure you don't want to use these plans instead:\n",previous_plans)
        ans = ot.request_info("Use one of these plans instead (y/n):\n{}".format(previous_plans))
        if ans == 'y':
            print('Enter the plan into the build script')
            sys.exit()
    print('Will continue to exclude parts in previous plans')

    past_builds = pd.read_sql_query("SELECT builds.build_name FROM builds", con=engine).build_name.tolist()
    if len(past_builds) == 0:
        last_build = 0
    else:
        last_build = [int(build[-3:]) for build in past_builds][-1]
    print('last_build',last_build)
    new_builds = ["build"+str(last_build+num).zfill(3) for num in range(1,num_builds+1)]
    print(new_builds)

    acceptable_status = ['received'] # List with all of the status that you want to pull from
    rework = ['cloning_error','cloning_failure','trans_failure']

    def list_to_string(ls):
        string = ''
        for l in ls:
            string += "'{}',".format(l)
        return string[:-1]

    def query_for_parts(status,enzyme,engine):
        query_parts = "SELECT parts.part_id,parts.status,fragments.fragment_name,plates.plate_id,wells.address,wells.volume,plates.id FROM parts\
                INNER JOIN part_frag ON parts.id = part_frag.part_id\
                INNER JOIN fragments ON part_frag.fragment_id = fragments.id\
                INNER JOIN wells ON fragments.id = wells.fragment_id\
                INNER JOIN plates on wells.plate_id = plates.id\
                WHERE parts.status IN ({})\
                    AND parts.cloning_enzyme = '{}'".format(list_to_string(status),enzyme)

        return pd.read_sql_query(query_parts, con=engine)

    for build,frag_num in zip(new_builds,frag_nums):
        ## Pulls in parts that have the desirable status and then sorts them by the plates their fragments are in,
        ## sorts them by their plate numbers and returns the earliest set of parts
        ot.print_center("...Finding genes for {}...".format(build))

        to_build = query_for_parts(acceptable_status,enzyme,engine)

        to_build.id = to_build.plate_id.apply(lambda x: 0 if x in used_plates else 1)

        grouped = to_build.groupby('part_id').filter(lambda x: len(x) == x['id'].sum())
        # print(grouped)
        if frag_num != 0:
            grouped = grouped.groupby('part_id').filter(lambda x: len(x) == frag_num)

        sort_group = grouped.sort_values('plate_id')
        # print(sort_group)
        sub = sort_group.part_id.unique().tolist()

        if len(sub) < max_rxns:
            print('Not enough remaining parts to fill a build')
            rework_ans = ot.request_info("Include previously failed genes? (y/n): ",type='string')
            if rework_ans == 'y':
                rework_df = query_for_parts(rework,enzyme,engine).sort_values('plate_id')
                sort_group = pd.concat([sort_group,rework_df])
                sub = sort_group.part_id.unique().tolist()[:max_rxns]
        else:
            print('truncating parts')
            sub = sub[:max_rxns]
            print(len(sub))

        to_build = sort_group[sort_group.part_id.isin(sub)]
        print(len(to_build))
        # print('To Build:\n',to_build)
        num_reactions = len(to_build)

        print("{} includes {} parts".format(build,len(to_build.part_id.unique().tolist())))

        print("Fragments sourced from each plate: ")
        print(to_build.groupby('part_id').agg(len).status.value_counts())

        used_plates += to_build.plate_id.unique().tolist()
        print(to_build.plate_id.unique().tolist())

        master_mix = ot.make_gg_rxns(num_reactions,10)
        print("Below is the required master mix for {}\n{}".format(build,master_mix))

        build_parts = to_build.part_id.unique().tolist()

        if args.run:

            ot.print_center('...Updating the database...')

            build_objs = [part for part in session.query(Part).filter(Part.part_id.in_(build_parts))]

            target_build = Build(build_objs,build_name=build)
            target_build.status = 'planning'
            session.add(target_build)

            for part in build_objs:
                part.change_status('planning')

            session.commit()
        else:
            print('Did not write plan. Add -r to the end of the function to write them')




if __name__ == "__main__":
    session,engine = connect_db()

    df = ot.query_everything(engine)
    df_rec = df[df.status == 'received']
    print('Below is the breakdown of remaining constructs by fragment number:')
    print(df_rec.Fragments.value_counts())


    print('1 build = 96 parts\n2 builds = 192 parts\n3 builds = 288 parts')
    num_parts = int(ot.request_info("Enter the desired number of parts to build: ",type='int'))
    num_builds = math.ceil(num_parts / 96)
    frag_nums = ot.request_info("Enter the number of fragments desired for the {} builds \n(i.e. '1 2 0' where 0 represents any number): ".format(num_builds), type='list',length=num_builds)

    enzyme_num = ot.request_info("Which enzyme (1 - BbsI or 2 - BtgZI): ",type='int')
    if int(enzyme_num) == 1:
        enzyme = 'BbsI'
    else:
        enzyme = 'BtgZI'
    print("Using enzyme: ",enzyme)

    create_build_plans(session,engine,num_parts,frag_nums,enzyme=enzyme)



import shutil

from config import *
from db_config import *
import ot_functions as ot

session,engine = connect_db()

## Query the database for the parts to be sequenced and also pulls in their fragment sequences
builds = []
print('Builds awaiting a manual check:')
for i,build in enumerate(session.query(Build).filter(Build.status == 'sequencing').order_by(Build.build_name)):
    print('{}: {}'.format(i,build.build_name))
    builds.append(build)

ans = ot.request_info('Which build would you like to check: ',type='int',select_from=[x for x in range(len(builds))])
tar_build = builds[ans]

target = tar_build.build_name

length = shutil.get_terminal_size().columns


fasta_path = "{}/builds/{}/{}_fasta_files".format(BASE_PATH,target,target)

query_wells = "SELECT parts.part_id,parts.cloning_enzyme,parts.seq,parts.part_name,fragments.fragment_name,fragments.seq as frag_seq,wells.address,wells.plate_type,wells.vector,builds.build_name,wells.misplaced FROM parts \
        INNER JOIN wells ON parts.id = wells.part_id\
        INNER JOIN plates ON wells.plate_id = plates.id\
        INNER JOIN builds ON plates.build_id = builds.id\
        INNER JOIN part_frag ON parts.id = part_frag.part_id\
        INNER JOIN fragments ON part_frag.fragment_id = fragments.id\
Exemple #9
0
def plate(session,engine):

    ot.print_center("============ Beginning to plate ============")

    # Take in command line arguments
    parser = argparse.ArgumentParser(description="Resuspend a plate of DNA on an Opentrons OT-1 robot.")
    parser.add_argument('-r', '--run', required=False, action="store_true", help="Send commands to the robot and print command output.")
    parser.add_argument('-m', '--manual', required=False, action="store_true", help="Maunal entry of parameters.")
    args = parser.parse_args()

    # Verify that the correct robot is being used
    if args.run:
        ot.check_robot()

    assemblies = []
    print("Choose which plate you would like to plate:")
    for index,assembly in enumerate(session.query(Plate).join(Build,Plate.builds)\
                .filter(Build.status == 'building').filter(Plate.transformed == 'transformed')\
                .filter(Plate.plated != 'plated').order_by(Build.build_name)):
        print("{}. {}".format(index,assembly.builds.build_name))
        assemblies.append(assembly)

    plate_num = ot.request_info("Enter plate here: ",type='int')
    target_plate = assemblies[plate_num]
    build_map = target_plate.wells
    if len(target_plate.wells) > 48:
        print("Too many samples to plate at once")
        portion = int(ot.request_info("Choose which half to plate, 1 or 2: ",type='int'))
        if portion == 1:
            build_map = target_plate.wells[:48]
        else:
            build_map = target_plate.wells[48:]
        num_reactions = len(build_map)
    else:
        portion = 1
        num_reactions = len(build_map)

    num_rows = math.ceil(num_reactions / 8)
    print("num rows: ",num_rows)
    trans_per_plate = 3
    num_plates = num_rows // trans_per_plate

    agar_plate_names = []
    for plate_num in range(num_plates):
        if portion == 2:
            plate_num += 2
        current_plate = target_plate.builds.build_name + "_p" + str(plate_num + 1)
        agar_plate_names.append(current_plate)
    print(agar_plate_names)
    print("You will need {} agar plates".format(len(agar_plate_names)))

    ## =============================================
    ## SETUP THE OT-1 DECK
    ## =============================================
    # Allocate slots for the required agar plates
    AGAR_SLOTS = ['D2','D3']
    layout = list(zip(agar_plate_names,AGAR_SLOTS[:len(agar_plate_names)]))
    print(layout)
    # Specify the locations of each object on the deck
    locations = {
                "tiprack-200" : "A3",
                "tiprack-10_2" : "E3",
                "tiprack-10_3" : "E2",
                "tiprack-10_1" : "E1",
                "trash" : "D1",
                "PCR-strip-tall" : "C3",
                "Transformation" : "C2",
                "Tube_rack" : "B1"
            }
    locations.update(dict(layout))
    ot.print_layout(locations)

    ## Initialize the OT-1
    ## ============================================

    # Determine whether to simulate or run the protocol
    if args.run:
        port = os.environ["ROBOT_DEV"]
        print("Connecting robot to port {}".format(port))
        robot.connect(port)
    else:
        print("Simulating protcol run")
        robot.connect()

    # Start timer
    start = datetime.now()
    print("Starting run at: ",start)

    # Start up and declare components on the deck
    robot.home()

    p200_tipracks = [
        containers.load('tiprack-200ul', locations['tiprack-200']),
    ]

    p10_tipracks = [
        containers.load('tiprack-10ul', locations['tiprack-10_2']),
        containers.load('tiprack-10ul', locations['tiprack-10_3']),
        containers.load('tiprack-10ul', locations['tiprack-10_1'])
    ]
    p10s_tipracks = [
    ]

    transformation_plate = containers.load('96-PCR-tall', locations['Transformation'])
    trash = containers.load('point', locations['trash'], 'holywastedplasticbatman')
    centrifuge_tube = containers.load('tube-rack-2ml',locations['Tube_rack'])
    master = containers.load('PCR-strip-tall', locations['PCR-strip-tall'])

    agar_plates = {}
    for plate, slot in layout:
        agar_plates[plate] = containers.load('96-deep-well', slot)
        print("agar_plates", agar_plates[plate])
    print("agar_plates",agar_plates,"\n")

    p10,p10s,p200 = ot.initialize_pipettes(p10_tipracks,p10s_tipracks,p200_tipracks,trash)


    def agar_plating(pipette,row,volume,calibrate,z):
        '''
        Dispenses the cells to be plated prior to reaching the agar and then
        stabs the tips slightly into the agar such that the droplets that pull
        up on the side of the tips make contact with the agar
        '''
        pipette.dispense(volume-1,row.top())
        pipette.dispense(1,row.bottom())
        if calibrate:
            calibrate,z = ot.change_height(p10,agar_plates[plate],agar_plates[plate].rows(plating_row)[0],recalibrate=True)
        return calibrate,z

    num_dilutions = 4
    plate_vol = 7.5
    dilution_vol = 9
    waste_vol = 2.5
    plating_row = 0

    calibrate = True
    z = 0
    media_per_tube = 150
    plate = agar_plate_names[0]
    plate_counter = 0

    # Aliquot the LB into the PCR tube strip for the dilutions
    p200.pick_up_tip()
    for well in range(8):
       print("Transferring {}ul to tube {}".format(media_per_tube,well))
       p200.transfer(media_per_tube, centrifuge_tube['A1'].bottom(),master.wells(well).bottom(),new_tip='never')
    p200.drop_tip()

    #input("Start the other run")
    # Iterate through each row of the transformation plate
    for trans_row in range(num_rows):
        # Iterates through each dilution
        for dilution in range(num_dilutions):
            # Resets to switch to the next plate
            if plating_row == 12:
                p200.pick_up_tip()
                for well in range(8):
                    print("Transferring {}ul to tube {}".format(media_per_tube,well))
                    p200.transfer(media_per_tube, centrifuge_tube['B1'].bottom(),master.wells(well).bottom(),new_tip='never')
                p200.drop_tip()
                plate = agar_plate_names[1]
                print("changing to :", plate)
                plating_row = 0
                calibrate = True
            print("trans_row",trans_row, "dilution",dilution,"plate",plate,"plate row",plating_row)
            p10.pick_up_tip()
            print("Diluting cells in row {} with {}ul".format(trans_row, dilution_vol))
            p10.transfer(dilution_vol, master['A1'].bottom(), transformation_plate.rows(trans_row).bottom(),new_tip='never',mix_before=(1,9))
            print("Plating {}ul from transformation row {} onto {} in row {}".format(plate_vol,trans_row,plate,plating_row))
            p10.aspirate(plate_vol,transformation_plate.rows(trans_row).bottom())

            calibrate,z = agar_plating(p10,agar_plates[plate].rows(plating_row),plate_vol,calibrate,z)

            print("Discard {}ul from transformation row {} into waste tube".format(waste_vol,trans_row))
            p10.aspirate(waste_vol,transformation_plate.rows(trans_row).bottom())
            p10.drop_tip()
            plating_row += 1
    stop = datetime.now()
    print(stop)
    runtime = stop - start
    print("Total runtime is: ", runtime)
    print("Rehoming")
    robot.home()

    commit = ot.request_info("Commit changes? (y/n): ",type='string',select_from=['y','n'])
    if commit == 'y':
        if len(target_plate.wells) <= 48:
            target_plate.plated = 'plated'
        else:
            ans = ot.request_info('Plated both halves of the build? (y/n): ',type='string',select_from=['y','n'])
            if ans == 'y':
                target_plate.plated = 'plated'
        session.commit()
    return
def frag_assign():
    ## Build a list of the previously entered synthesis plates
    plates_made = [
        plate.plate_name for plate in session.query(Plate).filter(
            Plate.plate_type == 'syn_plate')
    ]
    print("Plates that have already been parsed:\n", plates_made)

    ## Stores the last used plate ID so that it can increment
    previous_plates = [plate for plate in session.query(Plate).filter(Plate.plate_type == 'syn_plate')\
        .order_by(Plate.plate_id)]
    if len(previous_plates) == 0:
        print('No previous plate ID, starting at 001')
        current_id = 1
    else:
        last_plate = previous_plates[-1]
        print('last ID ', last_plate.plate_id)
        current_id = int(last_plate.plate_id[-3:]) + 1

    ## Take in plate maps from twist and generate fragment objects
    for file in glob.glob('{}/src/data/plate_maps/*.csv'.format(BASE_PATH)):
        plate_map = pd.read_csv(file)
        ## Have to account for two different csv formats
        try:
            plate_map['Plate']
            version = 1
            print('version', version)
            plate_key = 'Plate'
            well_key = 'Well'
            name_key = 'customer_line_item_id'
            sequence_key = 'Insert Sequence'
            yield_key = 'Yield (ng)'
        except:
            try:
                plate_map['Plate ID']
                version = 2
                print('version', version)
                plate_key = 'Plate ID'
                well_key = 'Well Location'
                name_key = 'Name'
                sequence_key = 'Insert sequence'
                yield_key = 'Yield (ng)'
            except:
                print(
                    "Doesn't fit the standard formats, please check column names in {}"
                    .format(file))
                sys.exit(0)
        unique = plate_map[plate_key].unique()
        for plate in unique:
            # Skips all of the plates that have already been parsed and added to the database
            if plate in plates_made:
                print('Plate {} has already been added'.format(plate))
                continue
            ot.print_center("...Parsing plate {}...".format(plate))
            # Create a subset of rows pertaining to a specific plate
            current_plate_map = plate_map[plate_map[plate_key] == plate]
            current_plate = Plate('',
                                  'syn_plate',
                                  plate,
                                  plate_id='syn_plate' +
                                  str(current_id).zfill(3))
            current_id += 1

            session.add(current_plate)
            for index, row in current_plate_map.iterrows():
                current_frag = ''
                current_frag = session.query(Fragment).filter(
                    Fragment.seq == row[sequence_key]).first()
                # Checks if a corresponding fragment is found and then adds it to the plate
                if current_frag:
                    current_plate.add_item(current_frag,
                                           address=row[well_key].strip(),
                                           syn_yield=row[yield_key])
                else:
                    new_frag = Fragment(fragment_name=row[name_key],
                                        seq=row[sequence_key].upper(),
                                        has_part='False')
                    current_plate.add_item(new_frag,
                                           address=row[well_key].strip(),
                                           syn_yield=row[yield_key])

    ot.print_center('...Updating part status...')
    for part in session.query(Part).join(Fragment,Part.fragments).join(Well,Fragment.wells)\
            .join(Plate,Well.plates).filter(Plate.plate_name.notin_(plates_made)):
        # print(part.part_id)
        no_build = False
        for frag in part.fragments:
            if not frag.wells:
                no_build = True
        if not no_build:
            part.change_status('received')
        session.add(part)

    commit = int(ot.request_info("Commit changes (1-yes, 2-no): ", type='int'))
    if commit == 1:
        session.commit()
        current_ids = pd.read_sql_query(
            "SELECT plates.plate_name,plates.plate_id FROM plates WHERE plates.plate_type = 'syn_plate'",
            con=engine)
        print(current_ids)
        print(
            "Write the ID on the bottom left corner of the front edge of each plate"
        )
def assess_plate():
    # Determine which build/plate to assess
    assemblies = []
    print("Choose which build you would like to assess:")
    for index,assembly in enumerate(session.query(Plate).join(Build,Plate.builds)\
                .filter(Build.status == 'building').filter(Plate.plated == 'plated')\
                .filter(Plate.assessed == 'not_assessed').order_by(Build.build_name)):
        print("{}. {}".format(index, assembly.builds.build_name))
        assemblies.append(assembly)
    plate_num = ot.request_info("Enter plate here: ", type='int')
    target_plate = assemblies[plate_num]

    # build_num = int(input("Enter build here: "))
    # target_build = assemblies[build_num]
    print("Will assess: ", target_plate.builds.build_name)

    ## =============================================
    ## QUERY DATABASE FOR REPLACEMENTS
    ## =============================================

    # Take in the wells that didn't work
    print("Enter well addresses that didn't work. e.g. A1 H6")
    failed = [x for x in input("List: ").split(" ")]
    print("Will exclude the following:", failed)

    ot.print_center('...Generating sequencing plate...')

    # Generate an empty sequencing plate and update the build status
    seq_plate = Plate([], 'seq_plate',
                      '{}-seq'.format(target_plate.builds.build_name))
    target_plate.builds.plates.append(seq_plate)
    target_plate.builds.status = 'sequencing'
    session.add(seq_plate)

    # Change the status of the wells and add successful ones to seq_plate
    for well in target_plate.builds.plates[0].wells:
        if well.address in failed:
            well.trans_outcome = 'trans_failure'
        else:
            well.trans_outcome = 'trans_success'
            seq_plate.add_item(well.parts, address=well.address)

    # Query the plate to find how many wells it has already used
    used_wells = [well for well in seq_plate.wells]
    remaining = 96 - len(used_wells)
    print("Remaining: ", remaining)
    if remaining != 0:
        ans = ot.request_info("Backfill empty wells? (y/n): ",
                              type='string',
                              select_from=['y', 'n'])
        if ans == 'n':
            remaining = 0
    # Pull parts that need to the retried
    acceptable_status = [
        'sequence_failure', 'cloning_mutation'
    ]  # List with all of the status that you want to pull from
    replacement_parts = [part for part in session.query(Part).join(Well,Part.wells)\
                .join(Plate,Well.plates).join(Build,Plate.builds).filter(Part.status\
                .in_(acceptable_status)).order_by(Build.id.desc(),Well.id)]

    # Find the wells that house the parts with the desired status
    rep_wells = []
    for part in replacement_parts:
        skip = False
        for well in part.wells:
            if skip:
                continue
            if well.seq_outcome == part.status:
                rep_wells.append(well)
                skip = True
                continue

    # Limit the number of wells to the number required and add it to the seq plate
    rep_wells = rep_wells[:remaining]
    part_to_well = dict(zip(replacement_parts[:remaining], rep_wells))
    for well in rep_wells:
        seq_plate.add_item(well.parts)
        print(well.parts.part_id, well.plates.builds.build_name, well.address)

    # Generate a dictionary to sort on well addresses A1-H1 instead of A1-A12
    well_index = ot.well_addresses()
    well_index = enumerate(well_index)
    well_index = [(y, x) for x, y in well_index]
    well_index = dict(well_index)

    # Generate a dictionary to link the parts to the well with the desired status
    part_to_well = dict(zip(replacement_parts[:remaining], rep_wells))

    # Iterate through the plate and add the necessary information for the map
    parts = []
    build_names = []
    source_wells = []
    target_wells = []
    target_index = []
    # Add all of the needed info to build a map
    for well in seq_plate.wells:
        parts.append(well.parts.part_id)

        # Finds parts coming from a different build and specifies where its coming from
        if well.parts in part_to_well.keys():
            build_names.append(
                '__' + part_to_well[well.parts].plates.builds.build_name +
                '__')
            source_wells.append(part_to_well[well.parts].address)
        else:
            build_names.append(well.plates.builds.build_name)
            source_wells.append(well.address)
        target_wells.append(well.address)
        target_index.append(well_index[well.address])

    # Build the plate map and sort based on the well index
    seq_plate_map = pd.DataFrame({
        'Part': parts,
        'Build': build_names,
        'Source Well': source_wells,
        'Target Well': target_wells,
        'Target Index': target_index
    })
    seq_plate_map = seq_plate_map.set_index('Target Index')
    seq_plate_map = seq_plate_map.sort_index()
    print(seq_plate_map)

    # Print the plates that are required
    unique_builds = pd.Series(seq_plate_map['Build']).unique()
    print("You will need the following builds:")
    print(unique_builds)

    # Generate a sequence plate map
    path = '{}/src/data/builds/{}/'.format(BASE_PATH,
                                           target_plate.builds.build_name)
    ot.make_directory(path)
    file_path = '{}/{}_seq_plate.csv'.format(path,
                                             target_plate.builds.build_name)
    seq_plate_map.to_csv(file_path, index=False)

    commit = ot.request_info("Commit changes? (y/n): ",
                             type='string',
                             select_from=['y', 'n'])
    if commit == 'y':
        target_plate.assessed = 'assessed'
        target_plate.builds.status = 'sequencing'
        session.commit()
    else:
        print('Not committed')
def transform(session, engine):
    # Take in command line arguments
    parser = argparse.ArgumentParser(
        description="Resuspend a plate of DNA on an Opentrons OT-1 robot.")
    parser.add_argument(
        '-r',
        '--run',
        required=False,
        action="store_true",
        help="Send commands to the robot and print command output.")
    # parser.add_argument('-m', '--manual', required=False, action="store_true", help="Maunal entry of parameters.")
    args = parser.parse_args()

    assemblies = []
    print("Choose which plate you would like to transform:")
    for index, assembly in enumerate(
            session.query(Plate).join(
                Build, Plate.builds).filter(Build.status == 'building').filter(
                    Plate.transformed == 'not_transformed').order_by(
                        Build.build_name)):
        print("{}. {}".format(index, assembly.builds.build_name))
        assemblies.append(assembly)

    plate_num = int(ot.request_info("Enter plate here: ", type='int'))
    target_plate = assemblies[plate_num]

    num_reactions = len(target_plate.wells)
    num_rows = num_reactions // 8

    # Verify that the correct robot is being used
    if args.run:
        ot.check_robot()

    # Specify the locations of each object on the deck
    locations = {
        "tiprack-200": "A3",
        "tiprack-10_2": "E2",
        "tiprack-10_3": "E3",
        "tiprack-10_1": "E1",
        "trash": "D1",
        "Transformation": "C2",
        "Build_plate": "C3",
        "Tube_rack": "B1"
    }
    ot.print_layout(locations)

    ## Initialize the OT-1
    ## ============================================

    # Determine whether to simulate or run the protocol
    if args.run:
        port = os.environ["ROBOT_DEV"]
        print("Connecting robot to port {}".format(port))
        robot.connect(port)
    else:
        print("Simulating protcol run")
        robot.connect()

    # Start timer
    start = datetime.now()
    print("Starting run at: ", start)

    # Start up and declare components on the deck
    robot.home()

    p200_tipracks = [
        containers.load('tiprack-200ul', locations['tiprack-200']),
    ]

    p10_tipracks = [
        containers.load('tiprack-10ul', locations['tiprack-10_2']),
        containers.load('tiprack-10ul', locations['tiprack-10_1']),
    ]

    p10s_tipracks = [
        containers.load('tiprack-10ul', locations["tiprack-10_3"])
    ]

    transformation_plate = containers.load('96-PCR-tall',
                                           locations['Transformation'])
    trash = containers.load('point', locations['trash'],
                            'holywastedplasticbatman')
    centrifuge_tube = containers.load('tube-rack-2ml', locations['Tube_rack'])
    build_plate = containers.load('96-PCR-tall', locations['Build_plate'])

    p10, p10s, p200 = ot.initialize_pipettes(p10_tipracks, p10s_tipracks,
                                             p200_tipracks, trash)

    dna_vol = 2

    for row in range(num_rows):
        p10.pick_up_tip()
        p10.transfer(dna_vol,
                     build_plate.rows(row).bottom(),
                     transformation_plate.rows(row).bottom(),
                     new_tip='never',
                     mix_before=(2, 9),
                     blow_out=True)
        print('Transferring DNA from row {}'.format(row))
        p10.drop_tip()
    if num_reactions % 8 > 0:
        p10s.pick_up_tip()
        print("need single channel for {}".format(num_reactions % 8))
        for missing in range(num_reactions % 8):
            current_well = (8 * num_rows) + (missing)
            print("Transferring {}ul of DNA to {}".format(
                dna_vol, current_well))
            p10s.transfer(dna_vol,
                          build_plate.wells(current_well).bottom(),
                          transformation_plate.wells(current_well).bottom(),
                          blow_out=True,
                          mix_before=(1, 8),
                          new_tip='never')
            p10s.drop_tip()
    commit = ot.request_info("Commit changes? (y/n): ",
                             type='string',
                             select_from=['y', 'n'])
    if commit == 'y':
        target_plate.transformed = 'transformed'
        session.commit()
    else:
        print('Not committed')
def resuspension(session, engine, target):
    ot.print_center("============ Beginning resuspension ============")

    # Initial Setup
    fmoles = 40

    # Load files
    parser = argparse.ArgumentParser(
        description="Resuspend a plate of DNA on an Opentrons OT-1 robot.")
    parser.add_argument(
        '-r',
        '--run',
        required=False,
        action="store_true",
        help="Send commands to the robot and print command output.")
    args = parser.parse_args()

    # Verify that the correct robot is being used
    if args.run:
        ot.check_robot()

    ## =============================================
    ## SETUP THE OT-1 DECK
    ## =============================================
    # Specify the locations of each object on the deck
    locations = {
        "tiprack-200": "A3",
        "tiprack-10": "E2",
        "tiprack-10s1": "E3",
        "tiprack-10s2": "E1",
        "trash": "D1",
        "PLATE HERE": "B2",
        "Trough": "B1"
    }
    ot.print_layout(locations)

    ot.print_center('...Calculating the volumes to resuspend with...')

    def calc_vol(amount, length, fmoles=40):
        return math.ceil(
            ((((amount * 1000) / (660 * length)) * 1000) / fmoles) * 2), fmoles

    total = 0
    for well in target.wells:
        length = len(well.fragments.seq)
        amount = well.syn_yield
        volume, conc = calc_vol(amount, length)
        well.volume = volume
        well.concentration = conc
        total += volume
        session.add(well)

    # print("total volume of water needed: {}uL".format(total))
    # num_tubes = math.ceil(total / 1000)
    # print("Prep {} tubes with 1.2mL".format(num_tubes))
    # input("Press Enter when you have added them to the tube rack")

    ## Initialize the OT-1

    # Determine whether to simulate or run the protocol
    if args.run:
        #port = robot.get_serial_ports_list()[0]
        port = os.environ["ROBOT_DEV"]
        print("Connecting robot to port {}".format(port))
        robot.connect(port)
    else:
        print("Simulating protcol run")
        robot.connect()

    start = datetime.now()
    print("Starting run at: ", start)

    # Start up and declare components on the deck
    robot.home()

    p200_tipracks = [
        containers.load('tiprack-200ul', locations["tiprack-200"]),
    ]

    p10_tipracks = [
        containers.load('tiprack-10ul', locations["tiprack-10"]),
    ]

    p10s_tipracks = [
        containers.load('tiprack-10ul', locations["tiprack-10s1"]),
        containers.load('tiprack-10ul', locations["tiprack-10s2"])
    ]

    trash = containers.load('point', locations["trash"],
                            'holywastedplasticbatman')
    centrifuge_tube = containers.load('trough-12row', locations["Trough"])
    # centrifuge_tube = containers.load('tube-rack-2ml',locations["Tube_rack"])

    resuspend_plate = containers.load('96-flat', locations["PLATE HERE"])

    p10, p10s, p200 = ot.initialize_pipettes(p10_tipracks, p10s_tipracks,
                                             p200_tipracks, trash)

    ## =============================================
    ## OT-1 PROTOCOL
    ## =============================================

    # Start timer
    start = datetime.now()
    print("Starting run at: ", start)

    tubes = dict({
        1: "A1",
        2: "B1",
        3: "C1",
        4: "D1",
        5: "A2",
        6: "B2",
        7: "C2",
        8: "D2",
        9: "A3",
        10: "B3",
        11: "C3",
        12: "D3",
        13: "A4",
        14: "B4",
        15: "C4"
    })
    tube_count = 1
    current_vol = 1200
    last_pipette = "neither"
    exclude_wells = []

    for target_well in target.wells:
        vol = target_well.volume
        well = target_well.address
        if well in exclude_wells:
            continue
        # Determine which pipette to use
        if vol < 20:
            # Makes sure that the robot doesn't pick up multiple sets of tips
            if last_pipette == "p200":
                print("Changing to p10s")
                p200.drop_tip()
                p10s.pick_up_tip()
            elif last_pipette == "neither":
                p10s.pick_up_tip()

            # Changes tubes of water when one gets low
            # if current_vol - vol < 200:
            #     tube_count += 1
            #     current_vol = 1200
            # current_vol -= vol
            print("Adding {}ul to well {} with the p10".format(vol, well))
            #p10s.transfer(vol, centrifuge_tube[tubes[tube_count]].bottom(), resuspend_plate.wells(well),touch_tip=True, blow_out=True, new_tip='never')
            p10s.transfer(vol,
                          centrifuge_tube['A1'],
                          resuspend_plate.wells(well),
                          touch_tip=True,
                          blow_out=True,
                          new_tip='never')
            # print("Currently {}ul in tube {}".format(current_vol,tubes[tube_count]))
            last_pipette = "p10s"
        else:
            if last_pipette == "p10s":
                print("Changing to p200")
                p10s.drop_tip()
                p200.pick_up_tip()
            elif last_pipette == "neither":
                p200.pick_up_tip()

            # Changes tubes of water when one gets low
            # if current_vol - vol < 100:
            #     tube_count += 1
            #     current_vol = 1200
            # current_vol -= vol
            print("Adding {}ul to well {} with the p200".format(vol, well))
            #p200.transfer(vol, centrifuge_tube[tubes[tube_count]].bottom(), resuspend_plate.wells(well),touch_tip=True, blow_out=True, new_tip='never')
            p200.transfer(vol,
                          centrifuge_tube['A1'],
                          resuspend_plate.wells(well),
                          touch_tip=True,
                          blow_out=True,
                          new_tip='never')
            # print("currently {}ul in tube {}".format(current_vol,tubes[tube_count]))
            last_pipette = "p200"

    # Last drop tip
    if last_pipette == "p10s":
        p10s.drop_tip()
    elif last_pipette == "p200":
        p200.drop_tip()

    stop = datetime.now()
    print(stop)
    runtime = stop - start
    print("Total runtime is: ", runtime)
    robot.home()

    target.resuspend()
    session.add(target)

    commit = int(ot.request_info("Commit changes (1-yes, 2-no): ", type='int'))
    if commit == 1:
        session.commit()

    ot.print_center('...Completed resuspension...')

    return
    commit = int(ot.request_info("Commit changes (1-yes, 2-no): ", type='int'))
    if commit == 1:
        session.commit()

    ot.print_center('...Completed resuspension...')

    return


if __name__ == "__main__":
    session, engine = connect_db()
    # Present all available plates to resuspend
    print("Which plate would you like to resuspend:")
    plate_names = []
    for index, plate in enumerate(
            session.query(Plate).filter(
                Plate.plate_type == 'syn_plate').filter(
                    Plate.resuspended != 'resuspended')):
        print("{}. {} - {}".format(index, plate.plate_id, plate.plate_name))
        plate_names.append(plate.plate_name)

    # Asks the user for a number corresponding to the plate they want to resuspend
    number = int(ot.request_info("Which plate: ", type='int'))
    target = session.query(Plate).filter(
        Plate.plate_name == plate_names[number]).first()
    print("Will resuspend plate ", target.plate_name)
    resuspension(session, engine, target)

#
from db_config import *
session,engine = connect_db()

# Create a csv file that has a build_name and failed column
# The failed column should be a string of well addresses with a space
# separating them
build_failures = pd.read_csv('./testing/plate_consolidation.csv')

# Separate the well addresses into a list
build_failures['wells'] = build_failures.failed.apply(lambda x: str(x).split(' '))

# State the build to be consolidated into the others
builds = build_failures.build_name.tolist()
for i, build in enumerate(builds):
    print('{}: {}'.format(i,build))
ans = ot.request_info('Choose which build to consolidate into the others: ',type='int')
extra_build = builds[ans]
    
# Pull out the build to consolidate
consolidate = build_failures[build_failures.build_name == extra_build]
consol_obj = session.query(Build).filter(Build.build_name == extra_build).one()

# Add in the result from the transformation on the consolidation plate
extras = []
for well in consol_obj.plates[0].wells:
    if well.address in consolidate.iloc[0].wells:
        print('fail')
        well.trans_outcome = 'trans_failure'
    else:
        well.trans_outcome = 'trans_success'
        extras.append(well.parts) # Store the parts that can be transferred
def run_build(session, engine):

    ot.print_center("============ Beginning build ============")

    # Take in the 'run' argument from the command line
    parser = argparse.ArgumentParser(
        description="Resuspend a plate of DNA on an Opentrons OT-1 robot.")
    parser.add_argument(
        '-r',
        '--run',
        required=False,
        action="store_true",
        help="Send commands to the robot and print command output.")
    args = parser.parse_args()

    # Verify that the correct robot is being used
    if args.run:
        ot.check_robot()

    # Choose which build plan to build
    build_options = []
    builds = [
        build
        for build in session.query(Build).filter(Build.status == 'planning')
    ]
    if len(builds) == 0:
        sys.exit(
            'No plans available, run `create_build_plan.py` to generate them')

    for num, build in enumerate([
            build for build in session.query(Build).filter(
                Build.status == 'planning')
    ]):
        print('{} - {}'.format(num, build.build_name))
        build_options.append(build)
    ans = ot.request_info("Enter desired plan number: ", type='int')
    target_build = build_options[ans]

    # Use that build name to create a dataframe with the information from the plan
    query = "SELECT parts.part_id,builds.build_name,part_wells.address as destination,fragments.fragment_name,frag_plates.plate_name,frag_plates.plate_id,frag_wells.address as source,frag_wells.volume FROM parts \
            INNER JOIN wells AS part_wells ON parts.id = part_wells.part_id\
            INNER JOIN plates AS part_plates ON part_wells.plate_id = part_plates.id\
            INNER JOIN builds ON part_plates.build_id = builds.id\
            INNER JOIN part_frag ON parts.id = part_frag.part_id\
            INNER JOIN fragments ON part_frag.fragment_id = fragments.id\
            INNER JOIN wells AS frag_wells ON fragments.id = frag_wells.fragment_id\
            INNER JOIN plates AS frag_plates ON frag_wells.plate_id = frag_plates.id\
            WHERE builds.build_name = '{}'".format(target_build.build_name)

    build_plan = pd.read_sql_query(query, con=engine)
    print(build_plan)
    frags = build_plan.groupby('part_id').agg(len)
    if len(frags) == len(
        [frag for frag in frags.fragment_name.tolist() if frag == 2]):
        print('Build only contains 2 fragment assemblies')
        num_frags = 2
        rxn_vol = 0.6
    else:
        print('Using MM for single fragment')
        rxn_vol = 0.8
        num_frags = 1

    unique_plates = build_plan.plate_id.unique().tolist()

    # Give each row a rank based on the order of the plates to sort on later
    plate_dict = dict([[y, x] for x, y in enumerate(unique_plates)])
    build_plan['plate_rank'] = build_plan.plate_id.apply(
        lambda x: plate_dict[x])
    build_plan = build_plan.sort_values('plate_rank')

    # Currently available spots on the OT-one deck
    SOURCE_SLOTS = ['D2', 'D3', 'B2']

    ## Generate a list of unique plates that are needed
    plate_index = [(y, x) for x, y in enumerate(unique_plates)]
    plate_index = dict(plate_index)

    ## Group the plates so that they can be swapped in batches
    ot.print_center("...Grouping plates...")
    group_plates = [
        unique_plates[n:n + len(SOURCE_SLOTS)]
        for n in range(0, len(unique_plates), len(SOURCE_SLOTS))
    ]
    for num, group in enumerate(group_plates):
        print("Group{}: {}".format(num + 1, group))

    ot.print_center("...Checking if plates need to be resuspended...")

    query_resuspend = "SELECT plates.plate_id,plates.resuspended FROM plates\
                        WHERE plates.resuspended = 'not_resuspended'\
                            AND plates.plate_id IN ({})".format(
        ot.list_to_string(unique_plates))

    resuspended = pd.read_sql_query(query_resuspend, con=engine)
    if len(resuspended) == 0:
        print('All plates are resuspended')
    else:
        for i, plate in resuspended.iterrows():
            ans = ot.request_info(
                'Plate {} is not resuspended, would you like to resuspend it? y/n: '
                .format(plate['plate_id']),
                type='string')
            if ans == 'y':
                resuspend.resuspension(
                    session, engine,
                    session.query(Plate).filter(
                        Plate.plate_id == plate['plate_id']).one())

    query = "SELECT parts.part_id,builds.build_name,part_wells.address as destination,fragments.fragment_name,frag_plates.plate_name,frag_plates.plate_id,frag_wells.address as source,frag_wells.volume FROM parts \
            INNER JOIN wells AS part_wells ON parts.id = part_wells.part_id\
            INNER JOIN plates AS part_plates ON part_wells.plate_id = part_plates.id\
            INNER JOIN builds ON part_plates.build_id = builds.id\
            INNER JOIN part_frag ON parts.id = part_frag.part_id\
            INNER JOIN fragments ON part_frag.fragment_id = fragments.id\
            INNER JOIN wells AS frag_wells ON fragments.id = frag_wells.fragment_id\
            INNER JOIN plates AS frag_plates ON frag_wells.plate_id = frag_plates.id\
            WHERE builds.build_name = '{}'".format(target_build.build_name)

    build_plan = pd.read_sql_query(query, con=engine)
    build_plan['plate_rank'] = build_plan.plate_id.apply(
        lambda x: plate_dict[x])
    build_plan = build_plan.sort_values('plate_rank')

    input("Press enter to continue")

    ## =============================================
    ## SETUP THE OT-1 DECK
    ## =============================================

    # Specify the locations of each object on the deck
    locations = {
        "tiprack-200": "A3",
        "tiprack-10": "E1",
        "tiprack-10s1": "E3",
        "tiprack-10s2": "E2",
        "trash": "D1",
        "PCR-strip-tall": "C3",
        "DEST_PLATE": "C2",
        "Tube_rack": "B1"
    }
    # Sets the first group of plates
    used_plates = []
    plate_counter = 0
    current_group = group_plates[plate_counter]
    source_plates = ot.change_plates(locations, current_group, SOURCE_SLOTS)

    ## =============================================
    ## SETUP THE MASTER MIX
    ## =============================================

    vol = int(
        ot.request_info('Enter desired reaction volume (i.e. 5,10,20): ',
                        type='int'))

    # Set the proportion of master mix to fragment to 4:1
    master_volume = rxn_vol * vol
    frag_vol = 0.2 * vol

    ot.print_center('...Calculating master mix volumes...')

    # Set a multiplier to account for pipetting error and evaporation
    extra_master = 1.3

    unique_frag = build_plan[['part_id', 'fragment_name',
                              'destination']].drop_duplicates()

    frag_df = unique_frag.groupby('destination').agg(len).part_id
    frag_df = frag_df.reset_index()
    frag_df = frag_df.rename(columns={'part_id': 'frag_num'})
    frag_dict = dict(
        zip(frag_df.destination.tolist(), frag_df.frag_num.tolist()))

    build_plan['frag_num'] = build_plan.destination.apply(
        lambda x: frag_dict[x])

    unique_df = build_plan[['part_id', 'destination',
                            'frag_num']].drop_duplicates()

    total_rxns = unique_df.frag_num.sum()

    need_extra = unique_df[unique_df.frag_num > 1]

    num_wells = len(build_plan.part_id.unique().tolist())
    num_rows = num_wells // 8
    master_reactions = math.ceil((total_rxns) * extra_master)
    print("Total rxns: {}".format(total_rxns, master_reactions))

    # Generate the dataframe to present the master mix composition
    master_mix = ot.make_gg_rxns(master_reactions, master_volume)
    print("Use the table below to create the master mix")
    print()
    print(master_mix)
    print()
    print("Place the master mix in the 'A1' position of the tube rack")
    print("Also place a tube of with 1.2 mL of water in the 'B1' position ")
    input("Press enter to continue")

    ## =============================================
    ## INITIALIZE THE OT-1
    ## =============================================
    # Determine whether to simulate or run the protocol

    if args.run:
        port = os.environ["ROBOT_DEV"]
        print("Connecting robot to port {}".format(port))
        robot.connect(port)
    else:
        print("Simulating protcol run")
        robot.connect()

    # Declare components on the deck
    p200_tipracks = [
        containers.load('tiprack-200ul', locations["tiprack-200"]),
    ]
    p10_tipracks = [
        containers.load('tiprack-10ul', locations["tiprack-10"]),
    ]
    p10s_tipracks = [
        containers.load('tiprack-10ul', locations["tiprack-10s1"]),
        containers.load('tiprack-10ul', locations["tiprack-10s2"])
    ]
    trash = containers.load('point', locations["trash"],
                            'holywastedplasticbatman')
    centrifuge_tube = containers.load('tube-rack-2ml', locations["Tube_rack"])
    master = containers.load('PCR-strip-tall', locations["PCR-strip-tall"])
    dest_plate = containers.load('96-PCR-tall', locations["DEST_PLATE"])

    p10, p10s, p200 = ot.initialize_pipettes(p10_tipracks, p10s_tipracks,
                                             p200_tipracks, trash)

    # Update database status
    def exit_handler():
        print('Choose one of the following options:')
        print('1-Save successful assembly\n2-Restore plan\n3-Abandon plan')
        ans = ot.request_info('Select what to do: ',
                              type='int',
                              select_from=[1, 2, 3])
        if ans == 1:
            ot.print_center('...Assembly is complete...')
        elif ans == 2:
            target_build.status = 'planning'
            ot.print_center('...Restoring the build plan...')
            for part in session.query(Part).filter(
                    Part.part_id.in_(build_plan.part_id.unique().tolist())):
                part.change_status('planning')
        elif ans == 3:
            target_build.status = 'abandoned'
            ot.print_center('...Unstaging all parts in build plan...')
            for part in session.query(Part).filter(
                    Part.part_id.in_(build_plan.part_id.unique().tolist())):
                part.change_status('received')
        session.commit()

    target_build.status = 'building'
    session.commit()

    atexit.register(exit_handler)

    ## =============================================
    ## OT-1 PROTOCOL
    ## =============================================

    # Start timer
    start = datetime.now()
    print("Starting run at: ", start)

    # Home the robot to start
    robot.home()

    # Aliquot the master mix into the PCR tube strip
    vol_per_tube = round((num_rows * master_volume * extra_master), 2)
    print("Aliquoting MM into PCR tubes")
    print("{}ul into each tube".format(vol_per_tube))
    p200.pick_up_tip()
    for well in range(8):
        print("Transferring {}ul to well {}".format(vol_per_tube, well))
        p200.transfer(vol_per_tube,
                      centrifuge_tube['A1'].bottom(),
                      master.wells(well).bottom(),
                      mix_before=(3, 50),
                      new_tip='never')
    p200.drop_tip()

    # Aliquot the master mix into all of the desired wells
    p10.pick_up_tip()
    for row in range(num_rows):
        print("Transferring {}ul of master mix to row {}".format(
            master_volume,
            int(row) + 1))
        p10.transfer(master_volume,
                     master['A1'].bottom(),
                     dest_plate.rows(row).bottom(),
                     mix_before=(1, 8),
                     blow_out=True,
                     new_tip='never')
    p10.drop_tip()

    # Aliquot master mix into the last row if not a complete row
    if num_wells % 8 > 0:
        p10s.pick_up_tip()
        print("need single channel for {}".format(num_wells % 8))
        for missing in range(num_wells % 8):
            current_well = (8 * num_rows) + (missing)
            print("Transferring {}ul of extra MM to {}".format(
                master_volume, current_well))
            p10s.transfer(master_volume,
                          centrifuge_tube['A1'].bottom(),
                          dest_plate.wells(current_well).bottom(),
                          blow_out=True,
                          mix_before=(1, 8),
                          new_tip='never')
        p10s.drop_tip()

    # Aliquot extra master mix into wells with multiple fragments
    if len(need_extra) != 0:
        p10s.pick_up_tip()
        for i, transfer in need_extra.iterrows():
            extra_volume = (int(transfer['frag_num']) - 1) * master_volume
            current_well = transfer['destination']
            print("Transferring {}ul of extra MM to {}".format(
                extra_volume, current_well))
            p10s.transfer(extra_volume,
                          centrifuge_tube['A1'].bottom(),
                          dest_plate.wells(current_well).bottom(),
                          blow_out=True,
                          mix_before=(1, 8),
                          new_tip='never')
        p10s.drop_tip()
    else:
        print('No extra MM must be aliquoted')

## Add the fragments from the source plates to the destination plate
## ============================================

# Sets the volume of water to dilute with, if needed
    dil_vol = 5

    build_plan = build_plan.sort_values('plate_rank')
    for i, row in build_plan.iterrows():
        start_well = row['source']
        dest_well = row['destination']
        gene = row['part_id']
        plate = row['plate_id']
        volume = row['volume']

        if plate not in current_group:
            plate_counter += 1
            current_group = group_plates[plate_counter]
            source_plates = ot.change_plates(locations, current_group,
                                             SOURCE_SLOTS)

        p10s.pick_up_tip()

        # Only dilutes wells that have low starting volume
        if volume < 30:
            print("Diluting sample in plate {} well {} with {}uL of water".
                  format(plate, start_well, dil_vol))
            p10s.transfer(dil_vol,
                          centrifuge_tube['B1'].bottom(),
                          source_plates[plate].wells(start_well).bottom(),
                          new_tip='never')

        print("Transferring {} of {} from plate {} well {} to well {}".format(
            frag_vol, gene, plate, start_well, dest_well))
        p10s.mix(3, 8, source_plates[plate].wells(start_well).bottom())

        # Checks the calibration to make sure that it can aspirate correctly
        p10s.aspirate(frag_vol,
                      source_plates[plate].wells(start_well).bottom())
        # if plate not in used_plates:
        #     ot.change_height(p10s,source_plates[plate],source_plates[plate].wells(start_well))
        p10s.dispense(frag_vol, dest_plate.wells(dest_well).bottom())
        used_plates.append(plate)

        p10s.drop_tip()

    robot.home()

    ot.print_center('...Updating part status...')

    to_build = [well.parts for well in target_build.plates[0].wells]
    for part in to_build:
        part.change_status('building')
    session.commit()

    return