Repository: EdinburghGenomics/clarity_scripts

Issue:
Add Check for poor quality genotype in convert_and_dispatch_genotypes.py
We need to check the quality of the genotype and make sure we have at least 20 good SNPs.
If not we need to set a UDF (to be named) so that the next step script can check if its value.

Discussion:

Patch:
diff --git a/EPPs/common.py b/EPPs/common.py
index ead0ee4..b734a33 100644
--- a/EPPs/common.py
+++ b/EPPs/common.py
@@ -71,9 +71,12 @@ class StepEPP(AppLogger):
             f = open(file_or_uid)
         else:
             a = Artifact(self.lims, id=file_or_uid)
-            f = StringIO(self.get_file_contents(uri=a.files[0].uri, encoding=encoding, crlf=crlf))
-
-        self.open_files.append(f)
+            if a.files:
+                f = StringIO(self.get_file_contents(uri=a.files[0].uri, encoding=encoding, crlf=crlf))
+            else:
+                f = None
+        if f:
+            self.open_files.append(f)
         return f
 
     # TODO: remove this when we switch to pyclarity_lims
diff --git a/scripts/convert_and_dispatch_genotypes.py b/scripts/convert_and_dispatch_genotypes.py
index 4cb8a39..18720e0 100644
--- a/scripts/convert_and_dispatch_genotypes.py
+++ b/scripts/convert_and_dispatch_genotypes.py
@@ -1,10 +1,13 @@
 #!/usr/bin/env python
 import csv
+from collections import defaultdict
 from os import remove
 from os.path import join, dirname, abspath
-from collections import defaultdict
+
+import sys
+from egcg_core.app_logging import AppLogger
 from egcg_core.config import Configuration
-from egcg_core.app_logging import AppLogger, logging_default as log_cfg
+
 import EPPs
 from EPPs.common import StepEPP, step_argparser
 
@@ -13,7 +16,6 @@ snp_cfg = Configuration(join(etc_path, 'SNPs_definition.yml'))
 default_fai = join(etc_path, 'genotype_32_SNPs_genome_600bp.fa.fai')
 default_flank_length = 600
 
-logger = log_cfg.get_logger(__name__)
 SNPs_definitions = snp_cfg['GRCh37_32_SNPs']
 
 # Accepted valid headers in the SNP CSV file
@@ -24,29 +26,22 @@ HEADERS_ASSAY_ID = ['SNPName', 'Assay Name']
 vcf_header = ['#CHROM', 'POS', 'ID', 'REF', 'ALT', 'QUAL', 'FILTER', 'INFO', 'FORMAT']
 start_vcf_header = ["##fileformat=VCFv4.1", '##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">']
 
+genotype_udf_file_id = 'Genotyping results file id'
+output_genotype_udf_number_call = 'Number of Calls (This Run)'
+submitted_genotype_udf_number_call = 'Number of Calls (Best Run)'
+submitted_nb_genotype_tries = 'QuantStudio Data Import Completed #'
+
 
 class GenotypeConversion(AppLogger):
-    def __init__(self, input_genotypes_contents, accufill_content, mode, fai=default_fai, flank_length=default_flank_length):
+    def __init__(self, input_genotypes_contents, fai=default_fai, flank_length=default_flank_length):
         self.all_records = defaultdict(dict)
         self.sample_names = set()
         self.input_genotypes_contents = input_genotypes_contents
         self.fai = fai
         self.flank_length = flank_length
-        self.accufill_content = accufill_content
         self._valid_array_barcodes = None
-        self.info("Parsing genotypes in '%s' mode", mode)
-        if mode == 'igmm':
-            self.parse_genotype_csv()
-        elif mode == 'quantStudio':
-            if self.accufill_content:
-                self.parse_quantstudio_flex_genotype()
-            else:
-                msg = 'Missing Accufill log file to confirm Array ids please provide with --accufill_log'
-                self.critical(msg)
-                raise ValueError(msg)
-        else:
-            raise ValueError('Unexpected genotype format: %s' % mode)
 
+        self.parse_quantstudio_flex_genotype()
         self.info('Parsed %s samples', len(self.sample_names))
 
         reference_lengths = self._parse_genome_fai()
@@ -101,23 +96,6 @@ class GenotypeConversion(AppLogger):
                 return f
         raise ValueError('Could not find any valid fields in ' + str(observed_fieldnames))
 
-    def parse_genotype_csv(self):
-        for input_genotypes_content in self.input_genotypes_contents:
-            reader = csv.DictReader(input_genotypes_content, delimiter='\t')
-            fields = set(reader.fieldnames)
-
-            header_sample_id = self._find_field(HEADERS_SAMPLE_ID, fields)
-            header_assay_id = self._find_field(HEADERS_ASSAY_ID, fields)
-            header_call = self._find_field(HEADERS_CALL, fields)
-
-            for line in reader:
-                sample = line[header_sample_id]
-                if not sample or sample.lower() == 'blank':
-                    # Entries with blank as sample name are entries with water and no DNA
-                    continue
-                assay_id = line[header_assay_id]
-                self.add_genotype(sample, assay_id, line.get(header_call))
-
     def parse_quantstudio_flex_genotype(self):
         for input_genotypes_content in self.input_genotypes_contents:
             result_lines = []
@@ -139,17 +117,6 @@ class GenotypeConversion(AppLogger):
             header_assay_id = self._find_field(['Assay ID'], sp_header)
             header_call = self._find_field(['Call'], sp_header)
 
-            # Check the barcode is valid according to the accufill log file
-            if not parameters['Barcode'] in self.valid_array_barcodes:
-                msg = 'Array barcode %s is not in the list of valid barcodes (%s)' % (
-                    parameters['Barcode'],
-                    ', '.join(self.valid_array_barcodes)
-                )
-                self.critical(msg)
-                raise ValueError(msg)
-            else:
-                logger.info('Validate array barcode %s', parameters['Barcode'])
-
             for line in result_lines[1:]:
                 sp_line = line.split('\t')
                 sample = sp_line[sp_header.index(header_sample_id)]
@@ -158,13 +125,29 @@ class GenotypeConversion(AppLogger):
                     continue
                 assay_id = sp_line[sp_header.index(header_assay_id)]
                 snp_def = SNPs_definitions.get(assay_id)
+                if not snp_def:
+                    # Remove control wells
+                    continue
+                assay_id = sp_line[sp_header.index(header_assay_id)]
+                snp_def = SNPs_definitions.get(assay_id)
                 call = sp_line[sp_header.index(header_call)]
 
                 if not call == 'Undetermined':
-                    call_type, c = call.split()
-                    e1, e2 = c.split('/')
-                    a1 = snp_def.get(e1.split('_')[-1])
-                    a2 = snp_def.get(e2.split('_')[-1])
+                    sp_call = call.split()
+
+                    e1, e2 = ' '.join(sp_call[1:]).split('/')
+                    if e1 == 'Allele 1':
+                        a1 = snp_def.get('V')
+                    elif e1 == 'Allele 2':
+                        a1 = snp_def.get('M')
+                    else:
+                        a1 = snp_def.get(e1.split('_')[-1])
+                    if e2 == 'Allele 1':
+                        a2 = snp_def.get('V')
+                    elif e2 == 'Allele 2':
+                        a2 = snp_def.get('M')
+                    else:
+                        a2 = snp_def.get(e2.split('_')[-1])
                     call = a1 + a2
                 self.add_genotype(sample, assay_id, call, parameters['Barcode'])
 
@@ -180,7 +163,8 @@ class GenotypeConversion(AppLogger):
                        snp_def['ref_base'], snp_def['alt_base'], ".", ".", ".", "GT"]
             self.all_records[snp_def['snp_id']]['SNP'] = snp
         if sample in self.all_records[snp_def['snp_id']]:
-            msg = 'Sample {} found more than once for SNPs {} while parsing {}'.format(sample, snp_def['snp_id'], array_barcode)
+            msg = 'Sample {} found more than once for SNPs {} while parsing {}'.format(sample, snp_def['snp_id'],
+                                                                                       array_barcode)
             self.critical(msg)
             raise Exception(msg)
         self.all_records[snp_def['snp_id']][sample] = genotype
@@ -216,102 +200,119 @@ class GenotypeConversion(AppLogger):
             open_file.write('\n'.join(lines))
         return vcf_file
 
-    def _parse_accufill_load_csv(self):
-        all_arrays = set()
-        reader = csv.DictReader(self.accufill_content, delimiter='\t')
-        header_holder = 'Plate Holder Position'
-        header_plate_barcode = 'Sample Plate Barcode'
-        header_array = 'OpenArray Plate Barcode'
-        for line in reader:
-            all_arrays.add((line[header_array], line[header_holder], line[header_plate_barcode]))
-        return all_arrays
-
-    @property
-    def valid_array_barcodes(self):
-        if not self._valid_array_barcodes:
-            array_info = self._parse_accufill_load_csv()
-            self._valid_array_barcodes = list(set([a for a, h, p in array_info]))
-        return self._valid_array_barcodes
+    def nb_calls(self, sample):
+        return sum([1 for snps_id in self.snps_order if self.all_records[snps_id].get(sample) != './.'])
 
 
 class UploadVcfToSamples(StepEPP):
-    def __init__(self, step_uri, username, password, log_file, mode, input_genotypes_files,
-                 accufill_log=None, no_upload=False):
+    def __init__(self, step_uri, username, password, log_file, input_genotypes_files):
         super().__init__(step_uri, username, password, log_file)
-        self.no_upload = no_upload
         input_genotypes_contents = []
         for s in input_genotypes_files:
-            input_genotypes_contents.append(self.open_or_download_file(s))
-        if accufill_log:
-            accufill_log_content = self.open_or_download_file(accufill_log)
+            f = self.open_or_download_file(s)
+            if f:
+                input_genotypes_contents.append(f)
+        self.geno_conv = GenotypeConversion(input_genotypes_contents, default_fai, default_flank_length)
+
+    def _find_output_art(self, input_art):
+        return [o.get('uri') for i, o in self.process.input_output_maps if
+                i.get('limsid') == input_art.id and o.get('output-generation-type') == 'PerInput']
+
+    def _upload_genotyping_for_one_sample(self, artifact):
+        lims_sample = artifact.samples[0]
+        vcf_file = self.geno_conv.generate_vcf(lims_sample.name)
+        nb_call = self.geno_conv.nb_calls(lims_sample.name)
+        output_arts = self._find_output_art(artifact)
+        # there should only be one
+        assert len(output_arts) == 1
+        # upload the number of calls to output
+        output_arts[0].udf[output_genotype_udf_number_call] = nb_call
+        output_arts[0].put()
+
+        # and the vcf file
+        lims_file = self.lims.upload_new_file(lims_sample, vcf_file)
+        # increment the nb of tries
+        if submitted_nb_genotype_tries not in lims_sample.udf:
+            lims_sample.udf[submitted_nb_genotype_tries] = 1
         else:
-            accufill_log_content = None
-
-        self.geno_conv = GenotypeConversion(input_genotypes_contents, accufill_log_content, mode, default_fai,
-                                            default_flank_length)
+            lims_sample.udf[submitted_nb_genotype_tries] += 1
+
+        if submitted_genotype_udf_number_call not in lims_sample.udf:
+            # This is the first genotyping results
+            lims_sample.udf[submitted_genotype_udf_number_call] = nb_call
+            lims_sample.udf[genotype_udf_file_id] = lims_file.id
+        elif lims_sample.udf.get(submitted_genotype_udf_number_call) and \
+                nb_call > lims_sample.udf.get(submitted_genotype_udf_number_call):
+            # This genotyping is better than before
+            lims_sample.udf[submitted_genotype_udf_number_call] = nb_call
+            lims_sample.udf[genotype_udf_file_id] = lims_file.id
+        else:
+            self.info(
+                'Sample %s new genotype has %s call(s), previous genotype has %s call(s)',
+                lims_sample.name,
+                nb_call,
+                lims_sample.udf[submitted_genotype_udf_number_call]
+            )
+        # finally upload the submitted samples
+        lims_sample.put()
+        remove(vcf_file)
 
     def _run(self):
         invalid_lims_samples = []
         valid_samples = []
         genotyping_sample_used = []
-        artifacts = self.process.all_inputs()
-        self.info('Matching against %s artifacts', len(artifacts))
-        for artifact in artifacts:
-            vcf_file = None
+
+        # First check that all sample are present and matching
+        self.info('Matching %s sample from file against %s artifacts',
+                  len(self.geno_conv.sample_names), len(self.artifacts))
+        for artifact in self.artifacts:
             # Assume only one sample per artifact
             lims_sample = artifact.samples[0]
-            if lims_sample.name in self.geno_conv.sample_names:
-                self.info('Matching %s' % lims_sample.name)
-                vcf_file = self.geno_conv.generate_vcf(lims_sample.name)
-                genotyping_sample_used.append(lims_sample.name)
-            elif lims_sample.udf.get('User Sample Name') in self.geno_conv.sample_names:
-                self.info('Matching %s against user sample name %s', lims_sample.name, lims_sample.udf.get('User Sample Name'))
-                vcf_file = self.geno_conv.generate_vcf(lims_sample.udf.get('User Sample Name'), new_name=artifact.name)
-                genotyping_sample_used.append(lims_sample.udf.get('User Sample Name'))
-            else:
+            if lims_sample.name not in self.geno_conv.sample_names:
                 self.info('No match found for %s', lims_sample.name)
                 invalid_lims_samples.append(lims_sample)
-            if vcf_file:
+            else:
+                self.info('Matching %s' % lims_sample.name)
+                genotyping_sample_used.append(lims_sample.name)
                 valid_samples.append(lims_sample)
-                if not self.no_upload:
-                    file = self.lims.upload_new_file(lims_sample, vcf_file)
-                    if file:
-                        lims_sample.udf['Genotyping results file id'] = file.id
-                        lims_sample.put()
-                remove(vcf_file)
 
         unused_samples = set(self.geno_conv.sample_names).difference(set(genotyping_sample_used))
 
-        self.info('Matched and uploaded %s artifacts against %s genotype results', len(set(valid_samples)), len(set(genotyping_sample_used)))
+        self.info('Matched and uploaded %s artifacts against %s genotype results', len(set(valid_samples)),
+                  len(set(genotyping_sample_used)))
         self.info('%s artifacts did not match', len(set(invalid_lims_samples)))
         self.info('%s genotyping results were not used', len(unused_samples))
 
-        # Message to print to stdout
+        # Message to print to stdout if there are missing samples
         messages = []
         if invalid_lims_samples:
             messages.append('%s Samples are missing genotype' % len(invalid_lims_samples))
         if len(self.geno_conv.sample_names) - len(valid_samples) > 0:
+            messages.append(
+                '%s genotypes have not been assigned' % (len(self.geno_conv.sample_names) - len(valid_samples)))
+
+        if messages:
             # TODO send a message to the EPP
-            messages.append('%s genotypes have not been assigned' % (len(self.geno_conv.sample_names) - len(valid_samples)))
-        print(', '.join(messages))
+            print(', '.join(messages))
+            sys.exit(1)
+
+        # All samples are present and matching: upload all samples
+        for artifact in self.artifacts:
+            self._upload_genotyping_for_one_sample(artifact)
+
 
 
 def main():
     args = _parse_args()
-    action = UploadVcfToSamples(args.step_uri, args.username, args.password, args.log_file, args.format,
-                                args.input_genotypes, args.accufill_log, args.no_upload)
+    action = UploadVcfToSamples(args.step_uri, args.username, args.password, args.log_file,
+                                args.input_genotypes)
     action.run()
 
 
 def _parse_args():
     p = step_argparser()
-    p.add_argument('--format', dest='format', type=str, choices=['igmm', 'quantStudio'],
-                   help='The format of the genotype file')
     p.add_argument('--input_genotypes', dest='input_genotypes', type=str, nargs='+',
                    help='The files or artifact id that contains the genotype for all the samples')
-    p.add_argument('--accufill_log', dest='accufill_log', type=str, required=False,
-                   help='The file that contains the location and name of each of the array')
-    p.add_argument('--no_upload', dest='no_upload', action='store_true', help='Prevent any upload to the LIMS')
     return p.parse_args()
 
 
diff --git a/scripts/populate_review_step.py b/scripts/populate_review_step.py
index 9cccfd0..3ba1948 100644
--- a/scripts/populate_review_step.py
+++ b/scripts/populate_review_step.py
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 import datetime
-from egcg_core import util
 from cached_property import cached_property
 from EPPs.common import StepEPP, RestCommunicationEPP, step_argparser
 from EPPs.config import load_config
@@ -19,8 +18,8 @@ class StepPopulator(StepEPP, RestCommunicationEPP):
             if io[0]['uri'].samples[0].name == sample_name and io[1]['output-type'] == 'ResultFile'
         ]
 
-    def check_rest_data_and_artifacts(self, sample_name):
-        query_args = {'where': {'sample_id': sample_name}}
+    def check_rest_data_and_artifacts(self, sample_name, selector):
+        query_args = {selector: {'sample_id': sample_name}}
         rest_entities = self.get_documents(self.endpoint, **query_args)
         artifacts = self.output_artifacts_per_sample(sample_name=sample_name)
         if len(rest_entities) != len(artifacts):  # in sample review this will be 1, in run review this will be more
@@ -31,18 +30,6 @@ class StepPopulator(StepEPP, RestCommunicationEPP):
             )
         return rest_entities, artifacts
 
-    def delivered(self, sample_name):
-        d = {'yes': True, 'no': False}
-        query_args = {'where': {'sample_id': sample_name}}
-        sample = self.get_documents('samples', **query_args)[0]
-        return d.get(sample.get('delivered'))
-
-    def processed(self, sample_name):
-        query_args = {'where': {'sample_id': sample_name}}
-        sample = self.get_documents('samples', **query_args)[0]
-        processing_status = util.query_dict(sample, 'aggregated.most_recent_proc.status')
-        return processing_status == 'finished'
-
     def _run(self):
         raise NotImplementedError
 
@@ -64,7 +51,7 @@ class PullInfo(StepPopulator):
         self.lims.put_batch(artifacts_to_upload)
 
     def add_artifact_info(self, sample):
-        rest_entities, artifacts = self.check_rest_data_and_artifacts(sample.name)
+        rest_entities, artifacts = self.check_rest_data_and_artifacts(sample.name, 'match')
         artifacts_to_upload = set()
         for i in range(len(rest_entities)):
             for art_field, api_field in self.metrics_mapping:
@@ -96,16 +83,15 @@ class PullInfo(StepPopulator):
 
 
 class PullRunElementInfo(PullInfo):
-    endpoint = 'run_elements'
+    endpoint = 'aggregate/run_elements'
     metrics_mapping = [
         ('RE Id', 'run_element_id'),
         ('RE Nb Reads', 'passing_filter_reads'),
-        ('RE Yield', 'aggregated.clean_yield_in_gb'),
-        ('RE Yield Q30', 'aggregated.clean_yield_q30_in_gb'),
-        ('RE %Q30', 'aggregated.clean_pc_q30'),
-        ('RE Coverage', 'coverage.mean'),
+        ('RE Yield', 'clean_yield_in_gb'),
+        ('RE Yield Q30', 'clean_yield_q30_in_gb'),
+        ('RE %Q30', 'clean_pc_q30'),
         ('RE Estimated Duplicate Rate', 'lane_pc_optical_dups'),
-        ('RE %Adapter', 'aggregated.pc_adaptor'),
+        ('RE %Adapter', 'pc_adapter'),
         ('RE Review status', 'reviewed'),
         ('RE Review Comment', 'review_comments'),
         ('RE Review date', 'review_date'),
@@ -116,6 +102,7 @@ class PullRunElementInfo(PullInfo):
 
     def assess_sample(self, sample):
         artifacts_to_upload = set()
+
         artifacts = self.output_artifacts_per_sample(sample_name=sample.name)
         un_reviewed_artifacts = [a for a in artifacts if a.udf.get('RE Review status') not in ['pass', 'fail']]
         if un_reviewed_artifacts:
@@ -124,69 +111,36 @@ class PullRunElementInfo(PullInfo):
 
         # Artifacts that pass the review
         pass_artifacts = [a for a in artifacts if a.udf.get('RE Review status') == 'pass']
+
         # Artifacts that fail the review
         fail_artifacts = [a for a in artifacts if a.udf.get('RE Review status') == 'fail']
-        # Artifacts that are new
-        new_artifacts = [a for a in artifacts if a.udf.get('RE previous Useable') not in ['yes', 'no']]
-
-        # skip samples which have been delivered, mark any new REs as such, not changing older RE comments
-        if self.delivered(sample.name):
-            for a in new_artifacts:
-                a.udf['RE Useable Comment'] = 'AR: Delivered'
-                a.udf['RE Useable'] = 'no'
-
-            for a in pass_artifacts + fail_artifacts:
-                if a.udf.get('RE previous Useable Comment') and a.udf.get('RE previous Useable'):
-                    a.udf['RE Useable Comment'] = a.udf.get('RE previous Useable Comment')
-                    a.udf['RE Useable'] = a.udf.get('RE previous Useable')
 
-            artifacts_to_upload.update(artifacts)
-            return artifacts_to_upload
+        target_yield = float(sample.udf.get('Yield for Quoted Coverage (Gb)'))
+        good_re_yield = sum([float(a.udf.get('RE Yield Q30')) for a in pass_artifacts])
 
-        # skip samples which have been processed, mark any new REs as such, not changing older RE comments
-        if self.processed(sample.name):
-            for a in pass_artifacts + fail_artifacts:
-                if a.udf.get('RE previous Useable Comment') and a.udf.get('RE previous Useable'):
-                    a.udf['RE Useable Comment'] = a.udf.get('RE previous Useable Comment')
-                    a.udf['RE Useable'] = a.udf.get('RE previous Useable')
-
-            for a in new_artifacts:
-                a.udf['RE Useable Comment'] = 'AR: Sample already processed'
+        # Just the right amount of good yield: take it all
+        if target_yield < good_re_yield < target_yield * 2:
+            for a in pass_artifacts:
+                a.udf['RE Useable'] = 'yes'
+                a.udf['RE Useable Comment'] = 'AR: Good yield'
+            for a in fail_artifacts:
                 a.udf['RE Useable'] = 'no'
-
+                a.udf['RE Useable Comment'] = 'AR: Failed and not needed'
             artifacts_to_upload.update(artifacts)
-            return artifacts_to_upload
-
-        target_yield = float(sample.udf.get('Required Yield (Gb)'))
-        good_re_yield = sum([float(a.udf.get('RE Yield')) for a in pass_artifacts])
-
-        # Increase target coverage by 5% to resolve borderline cases
-        target_coverage = 1.05 * sample.udf.get('Coverage (X)')
-        obtained_coverage = float(sum([a.udf.get('RE Coverage') for a in pass_artifacts]))
 
         # Too much good yield limit to the best quality ones
-        if good_re_yield > target_yield * 2 and obtained_coverage > target_coverage:
+        elif good_re_yield > target_yield * 2:
             # Too much yield: sort the good artifact by quality
             pass_artifacts.sort(key=lambda x: x.udf.get('RE %Q30'), reverse=True)
             current_yield = 0
             for a in pass_artifacts:
-                current_yield += float(a.udf.get('RE Yield'))
+                current_yield += float(a.udf.get('RE Yield Q30'))
                 if current_yield < target_yield * 2:
                     a.udf['RE Useable'] = 'yes'
                     a.udf['RE Useable Comment'] = 'AR: Good yield'
                 else:
                     a.udf['RE Useable'] = 'no'
-                    a.udf['RE Useable Comment'] = 'AR: Too much good yield'
-            for a in fail_artifacts:
-                a.udf['RE Useable'] = 'no'
-                a.udf['RE Useable Comment'] = 'AR: Failed and not needed'
-            artifacts_to_upload.update(artifacts)
-
-        # Just the right amount of good yield: take it all
-        elif target_yield < good_re_yield < target_yield * 2 or obtained_coverage > target_coverage:
-            for a in pass_artifacts:
-                a.udf['RE Useable'] = 'yes'
-                a.udf['RE Useable Comment'] = 'AR: Good yield'
+                    a.udf['RE Useable Comment'] = 'AR: To much good yield'
             for a in fail_artifacts:
                 a.udf['RE Useable'] = 'no'
                 a.udf['RE Useable Comment'] = 'AR: Failed and not needed'
@@ -199,16 +153,16 @@ class PullRunElementInfo(PullInfo):
 
 
 class PullSampleInfo(PullInfo):
-    endpoint = 'samples'
+    endpoint = 'aggregate/samples'
     metrics_mapping = [
-        ('SR Yield (Gb)', 'aggregated.clean_yield_in_gb'),
-        ('SR %Q30', 'aggregated.clean_pc_q30'),
-        ('SR % Mapped', 'aggregated.pc_mapped_reads'),
-        ('SR % Duplicates', 'aggregated.pc_duplicate_reads'),
-        ('SR Mean Coverage', 'aggregated.mean_coverage'),
-        ('SR Species Found', 'matching_species'),
-        ('SR Sex Check Match', 'aggregated.gender_match'),
-        ('SR Genotyping Match', 'aggregated.genotype_match'),
+        ('SR Yield (Gb)', 'clean_yield_in_gb'),
+        ('SR %Q30', 'clean_pc_q30'),
+        ('SR % Mapped', 'pc_mapped_reads'),
+        ('SR % Duplicates', 'pc_duplicate_reads'),
+        ('SR Mean Coverage', 'coverage.mean'),
+        ('SR Species Found', 'species_contamination'),
+        ('SR Sex Check Match', 'gender_match'),
+        ('SR Genotyping Match', 'genotype_match'),
         ('SR Freemix', 'sample_contamination.freemix'),
         ('SR Review Status', 'reviewed'),
         ('SR Review Comments', 'review_comments'),
@@ -238,9 +192,9 @@ class PullSampleInfo(PullInfo):
 
     def field_from_entity(self, entity, api_field):
         # TODO: remove once Rest API has a sensible field for species found
-        if api_field == 'matching_species':
-            species = entity[api_field]
-            return ', '.join(species)
+        if api_field == 'species_contamination':
+            species = entity[api_field]['contaminant_unique_mapped']
+            return ', '.join(k for k in sorted(species) if species[k] > 500)
 
         return super().field_from_entity(entity, api_field)
 
@@ -260,7 +214,7 @@ class PushInfo(StepPopulator):
         _ = self.output_artifacts
         for sample in self.samples:
             self.info('Pushing data for sample %s', sample.name)
-            rest_entities, artifacts = self.check_rest_data_and_artifacts(sample.name)
+            rest_entities, artifacts = self.check_rest_data_and_artifacts(sample.name, 'where')
             rest_api_data = {}
             for e in rest_entities:
                 rest_api_data[e[self.api_id_field]] = e
diff --git a/setup.py b/setup.py
index 404668a..ac46760 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,8 @@ version = '0.7.dev0'
 setup(
     name='clarity_scripts',
     version=version,
-    packages=find_packages(exclude=('tests',)),
+    packages=['EPPs'],
+    package_data={'EPPs': ['etc/*']},
     url='https://github.com/EdinburghGenomics/clarity_scripts',
     license='MIT',
     description='Clarity EPPs used in Edinburgh Genomics',


Test patch:
diff --git a/tests/assets/VGA55_QuantStudio 12K Flex_export.txt b/tests/assets/VGA55_QuantStudio 12K Flex_export.txt
deleted file mode 100755
index 7bbc637..0000000
--- a/tests/assets/VGA55_QuantStudio 12K Flex_export.txt
+++ /dev/null
@@ -1,86 +0,0 @@
-* Barcode = VGA55
-* Block Type = OpenArray Block
-* Chemistry = TAQMAN
-* Comment = NA
-* Date Created = 09-08-2016 15:39:38 PM BST
-* Date Modified = 09-08-2016 19:17:53 PM BST
-* Experiment File Name = C:\Applied Biosystems\QuantStudio 12K Flex Software\User Files\experiments\VGA55_2016_09_08_153938.eds
-* Experiment Name = VGA55
-* Experiment Run Start Time = 09-08-2016 15:40:56 PM BST
-* Experiment Run Stop Time = 09-08-2016 19:15:03 PM BST
-* Experiment Type = SNP Genotyping
-* Instrument Name = 285880963
-* Instrument Serial Number = 285880963
-* Instrument Type = QuantStudio 12K Flex
-* Passive Reference = 
-* Quantification Cycle Method = Crt
-* Signal Smoothing On = true
-* Stage/ Cycle where Analysis is performed = Stage 3, Step 3
-* User Name = NA
-
-[Results]
-Well	Well Position	Omit	Sample Name	Assay ID	Allele1 Name	Allele2 Name	Allele1 Dyes	Allele2 Dyes	NCBI SNP Reference	Context Sequence	Quality Value	SNP Assay Name	Task	Allele1 R	Allele2 R	ROX Signal	Quality(%)	Call	Method	Call Cycle	Allele1 Amp Score	Allele2 Amp Score	Allele1 Cq Conf	Allele2 Cq Conf	Allele 1 Crt	Allele 2 Crt	ALLELE2CRTNOISE	ALLELE1CRTNOISE	ALLELE1CRTAMPLITUDE	ALLELE2CRTAMPLITUDE
-1	A1a1	false	V0001P001A01	C__11821218_10	C__11821218_10_V	C__11821218_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4855056	"TAATTAGGATTATGTGTACTTGCCT[A/G]TGAGTTCTCAAATAGCTAATGATAC"	95.000	C__11821218_10	UNKNOWN	1,020.718	836.246	740.735	98.752	Heterozygous C__11821218_10_V/C__11821218_10_M	Auto	40	1.049	1.069	0.449	0.598	30.524	31.961	N	N	N	N
-2	A1a2	false	V0001P001A01	C___1083232_10	C___1083232_10_V	C___1083232_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2032598	"TAATGGCCAGCAATTTAGTATTGCC[T/C]GACTTTTACTAATGCATGTGCTGTT"	95.000	C___1083232_10	UNKNOWN	204.954	390.974	793.618	77.375	Undetermined	Auto	40	0.672	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-3	A1a3	false	V0001P001A01	C___1007630_10	C___1007630_10_V	C___1007630_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs441460	"CATGGAAACAGAGCCTGGATCATAC[A/G]GGGAACTGCAACCATGATTGGATTA"	95.000	C___1007630_10	UNKNOWN	760.956	2,675.284	871.529	98.898	Homozygous C___1007630_10_M/C___1007630_10_M	Auto	40	0.902	1.285	0.364	0.743	21.696	23.000	N	N	N	N
-4	A1a4	false	V0001P001A01	C__15935210_10	C__15935210_10_V	C__15935210_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2259397	"AGTAGGGACCAAGGAAAGGTCATCT[C/T]GGACATAATCGGGAGCTGTGGCTAA"	95.000	C__15935210_10	UNKNOWN	3,564.644	2,183.033	949.794	98.886	Heterozygous C__15935210_10_V/C__15935210_10_M	Auto	40	1.343	1.108	0.651	0.552	23.065	23.453	N	N	N	N
-5	A1a5	false	V0001P001A01	C___1563023_10	C___1563023_10_V	C___1563023_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2136241	"GGCAAATGTAGCACATGTGAGGGTA[C/T]AAAATTATGATGCTACAATCAGAAA"	95.000	C___1563023_10	UNKNOWN	591.356	2,932.495	896.176	98.879	Homozygous C___1563023_10_M/C___1563023_10_M	Auto	40	0.886	1.374	0.289	0.950	23.142	23.552	N	N	N	N
-6	A1a6	false	V0001P001A01	C___1902433_10	C___1902433_10_V	C___1902433_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10771010	"GAGTAAGGAAGGGTATACTAAGGAA[C/T]AGAGTCACCGGGAGGAGCAACTTCA"	95.000	C___1902433_10	UNKNOWN	-63.816	3,861.686	832.176	98.898	Homozygous C___1902433_10_M/C___1902433_10_M	Auto	40	0.740	1.361	0.000	0.786	Undetermined	23.813	N	Y	Y	N
-7	A1a7	false	V0001P001A01	C__11710129_10	C__11710129_10_V	C__11710129_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs11083515	"ACACCAACAATTTGCTGTTATCCAC[A/G]GTCACTCAGCTAAACCTATGCCCTG"	95.000	C__11710129_10	UNKNOWN	3,359.858	3,136.062	1,063.177	98.898	Heterozygous C__11710129_10_V/C__11710129_10_M	Auto	40	1.300	1.219	0.579	0.557	23.806	23.834	N	N	N	N
-8	A1a8	false	V0001P001A01	C___1027548_20	C___1027548_20_V	C___1027548_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs768983	"CCATACTATAAACGAACTGTGAGTA[T/C]GCTCCACCAATTCCAAACAAACGTT"	95.000	C___1027548_20	UNKNOWN	131.272	394.760	1,054.177	83.251	Undetermined	Auto	40	0.553	0.928	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-9	A1b1	false	V0001P001A01	C___1250735_20	C___1250735_20_V	C___1250735_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4751955	"CTGGTCTTCAGCTTCCCTTTTCAGA[A/G]TGGAATGCACACGGTAAGTTTGTGA"	95.000	C___1250735_20	UNKNOWN	2,741.215	4,148.047	882.176	98.898	Heterozygous C___1250735_20_V/C___1250735_20_M	Auto	40	1.277	1.365	0.569	0.728	22.585	22.332	N	N	N	N
-10	A1b2	false	V0001P001A01	C__11522992_10	C__11522992_10_V	C__11522992_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6598531	"GCTTTGCAAATTCAAGTGAAAGCTC[G/T]GCAGAAACACAAGCGGGGAAGCCCT"	95.000	C__11522992_10	UNKNOWN	1,409.446	4,052.771	855.706	98.898	Homozygous C__11522992_10_M/C__11522992_10_M	Auto	40	1.138	1.384	0.481	0.727	25.210	24.742	N	N	N	N
-11	A1b3	false	V0001P001A01	C___1122315_10	C___1122315_10_V	C___1122315_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs11660213	"TGTCTAGAGCTGTATGCTTTCATGT[A/G]GTAGCCAGTAGCCACATGTGGCTAT"	95.000	C___1122315_10	UNKNOWN	4,926.423	3,438.564	869.529	98.886	Heterozygous C___1122315_10_V/C___1122315_10_M	Auto	40	1.427	1.275	0.732	0.544	22.625	22.366	N	N	N	N
-12	A1b4	false	V0001P001A01	C___1801627_20	C___1801627_20_V	C___1801627_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10869955	"AAACCAGCAGAACAGTAACATAATC[A/C]TGGGAGTGACATCCCTTCACTCTTG"	95.000	C___1801627_20	UNKNOWN	5,232.941	2,699.524	856.265	98.898	Homozygous C___1801627_20_V/C___1801627_20_V	Auto	40	1.516	1.166	0.966	0.421	23.163	23.161	N	N	N	N
-13	A1b5	false	V0001P001A01	C__10076371_10	C__10076371_10_V	C__10076371_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4783229	"GGGTCTTCCTCAACATAGCACATAA[C/T]CTGGCTTTCTAGATATTTTCCTTAA"	95.000	C__10076371_10	UNKNOWN	5,347.260	4,650.109	835.500	98.886	Heterozygous C__10076371_10_V/C__10076371_10_M	Auto	40	1.573	1.469	0.982	0.968	23.159	23.468	N	N	N	N
-14	A1b6	false	V0001P001A01	C___1670459_10	C___1670459_10_V	C___1670459_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6554653	"CATGAAACCACCCGGCAAATACTTA[C/T]ACATAGACTGATTTAGAGTGGAAAA"	95.000	C___1670459_10	UNKNOWN	4,222.754	3,025.554	841.088	98.898	Heterozygous C___1670459_10_V/C___1670459_10_M	Auto	40	1.383	1.237	0.709	0.619	23.513	23.604	N	N	N	N
-15	A1b7	false	V0001P001A01	C__16205730_10	C__16205730_10_V	C__16205730_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2336695	"TAACTGATGTAGTGAAATGTTGAGA[A/G]AATCAGATATTAATGGTTGGCTGAG"	95.000	C__16205730_10	UNKNOWN	5,268.573	5,328.659	1,076.882	98.898	Heterozygous C__16205730_10_V/C__16205730_10_M	Auto	40	1.460	1.475	0.730	0.877	23.615	23.487	N	N	N	N
-16	A1b8	false	V0001P001A01	C__27402849_10	C__27402849_10_V	C__27402849_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6927758	"TCTGAAATAGTGCTTATTGCATCGA[C/T]CAAAAAGAAGTGAATGCTGGAGTGG"	95.000	C__27402849_10	UNKNOWN	902.983	3,573.698	913.647	98.879	Homozygous C__27402849_10_M/C__27402849_10_M	Auto	40	1.116	1.422	0.422	0.975	23.428	23.287	N	N	N	N
-17	A1c1	false	V0001P001A01	C___2728408_10	C___2728408_10_V	C___2728408_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3010325	"TTTTGTACCTAGGGTTGTCTGATAC[C/T]AAAGCTCAAGTTCATTCTACTTCGT"	95.000	C___2728408_10	UNKNOWN	510.448	4,620.122	891.618	98.898	Homozygous C___2728408_10_M/C___2728408_10_M	Auto	40	0.910	1.453	0.403	0.933	23.921	22.797	N	N	N	N
-18	A1c2	false	V0001P001A01	C__26546714_10	C__26546714_10_V	C__26546714_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7773994	"ATAATATGAAACACCATGCAATCAT[G/T]AAGAGATTTTAATGTATCTTATCAA"	95.000	C__26546714_10	UNKNOWN	3,285.366	1,858.643	844.912	98.898	Heterozygous C__26546714_10_V/C__26546714_10_M	Auto	40	1.275	1.145	0.560	0.600	23.267	22.390	N	N	N	N
-19	A1c3	false	V0001P001A01	C__26524789_10	C__26524789_10_V	C__26524789_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3742257	"CCCAGAGCTGTGCAGTGTAGTGCCC[C/T]GGGTCTAGGCAACAGCAGAAAGTGG"	95.000	C__26524789_10	UNKNOWN	2,024.071	2,259.527	829.500	98.898	Heterozygous C__26524789_10_V/C__26524789_10_M	Auto	40	1.192	1.179	0.498	0.502	22.740	22.961	N	N	N	N
-20	A1c4	false	V0001P001A01	C__31386842_10	C__31386842_10_V	C__31386842_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs12318959	"AAACATTTTCGGCATTGCTTATTGA[C/T]GATGGCAGGGAAAGTTGAAGTTTCC"	95.000	C__31386842_10	UNKNOWN	4,477.846	4,023.062	860.500	98.898	Heterozygous C__31386842_10_V/C__31386842_10_M	Auto	40	1.403	1.346	0.680	0.693	23.165	23.256	N	N	N	N
-21	A1c5	false	V0001P001A01	C__30044763_10	C__30044763_10_V	C__30044763_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10194978	"AAGGATTAGATAAAACAAAATCCGG[A/G]CACTTGACATGCTCAGGGATCAAAT"	95.000	C__30044763_10	UNKNOWN	4,038.190	3,469.543	849.412	98.886	Heterozygous C__30044763_10_V/C__30044763_10_M	Auto	40	1.407	1.370	0.725	0.854	23.679	23.806	N	N	N	N
-22	A1c6	false	V0001P001A01	C__29619553_10	C__29619553_10_V	C__29619553_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs9396715	"TCTCCTAGAAATCTTGGCCAAAGCA[C/T]AACTGGGTAAAAATAAGGAAAATAT"	95.000	C__29619553_10	UNKNOWN	5,732.102	4,183.877	830.559	98.898	Heterozygous C__29619553_10_V/C__29619553_10_M	Auto	40	1.528	1.396	0.891	0.765	23.696	23.811	N	N	N	N
-23	A1c7	false	V0001P001A01	C___2953330_10	C___2953330_10_V	C___2953330_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7796391	"TATTCTGCAAAGCCATAGGGACAAA[A/G]CTACCCAAGACCATGGGAACCCACC"	95.000	C___2953330_10	UNKNOWN	3,042.546	5,472.797	903.765	98.898	Homozygous C___2953330_10_M/C___2953330_10_M	Auto	40	1.294	1.636	0.451	0.978	26.655	22.447	N	N	N	N
-24	A1c8	false	V0001P001A01	C___8938211_20	C___8938211_20_V	C___8938211_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3913290	"AGAGCTCACAATCAAGCCTGGTGAC[T/C]TGAGACTAGCCCTTGTGCATTCATA"	95.000	C___8938211_20	UNKNOWN	145.950	267.808	923.912	79.091	Undetermined	Auto	40	0.664	0.731	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-25	A1d1	false	V0001P001A01	C___8924366_10	C___8924366_10_V	C___8924366_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1377935	"TAATAAAATATGTAAGCGCAAGATA[C/T]TATGCTGGGCACCAAATAGGACACC"	95.000	C___8924366_10	UNKNOWN	3,663.103	2,752.647	848.706	98.898	Heterozygous C___8924366_10_V/C___8924366_10_M	Auto	40	1.348	1.248	0.579	0.626	26.277	26.222	N	N	N	N
-26	A1d2	false	V0001P001A01	C___8850710_10	C___8850710_10_V	C___8850710_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1157213	"GTTCATCACAGGGAACAGTGGTCTT[C/T]CAATATCTTCCTAGTCAGGACCCAT"	95.000	C___8850710_10	UNKNOWN	2,240.700	1,780.808	782.618	98.898	Heterozygous C___8850710_10_V/C___8850710_10_M	Auto	40	1.189	1.099	0.496	0.488	23.209	23.937	N	N	N	N
-27	A1d3	false	V0001P001A01	C___7457509_10	C___7457509_10_V	C___7457509_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1567612	"ATGGTTCCAAATATTTTATGCGTAT[A/G]CTTTATCTCTTACTTGCAGTACATA"	95.000	C___7457509_10	UNKNOWN	4,380.590	3,873.021	805.000	98.892	Heterozygous C___7457509_10_V/C___7457509_10_M	Auto	40	1.530	1.415	0.987	0.900	22.986	23.708	N	N	N	N
-28	A1d4	false	V0001P001A01	C___7431888_10	C___7431888_10_V	C___7431888_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1533486	"TTGTCTCAACCATCCAGAAATCAGT[G/T]TATTGAATTGGTTGATTTGATGTTA"	95.000	C___7431888_10	UNKNOWN	4,947.120	261.837	799.088	98.898	Homozygous C___7431888_10_V/C___7431888_10_V	Auto	40	1.532	0.662	0.980	0.000	22.746	Undetermined	Y	N	N	Y
-29	A1d5	false	V0001P001A01	C___7421900_10	C___7421900_10_V	C___7421900_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1415762	"TTATGCAGAATACTAAAAAATCTTC[C/T]GAATCTTCTATCCTGTGTCTATCTT"	95.000	C___7421900_10	UNKNOWN	5,062.511	5,515.088	842.941	98.898	Heterozygous C___7421900_10_V/C___7421900_10_M	Auto	40	1.501	1.548	0.874	0.985	24.043	23.648	N	N	N	N
-30	A1d6	false	V0001P001A01	C_____43852_10	C_____43852_10_V	C_____43852_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs946065	"TGAGTGAGGTGTAGTTCACTTCATC[A/C]GGTAGAATCCCAACAAGCTGAGATG"	95.000	C_____43852_10	UNKNOWN	5,458.129	4,582.492	796.882	98.898	Heterozygous C_____43852_10_V/C_____43852_10_M	Auto	40	1.479	1.379	0.908	0.787	23.357	23.708	N	N	N	N
-31	A1d7	false	V0001P001A01	C__33211212_10	C__33211212_10_V	C__33211212_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7564899	"TATCCTGTAAAGAAGCATCATGCAA[A/G]GGACTTTAAGTCACAGTAATTTAAT"	95.000	C__33211212_10	UNKNOWN	4,268.200	289.616	811.412	98.898	Homozygous C__33211212_10_V/C__33211212_10_V	Auto	40	1.435	0.677	0.773	0.000	23.797	Undetermined	Y	N	N	Y
-32	A1d8	false	V0001P001A01	C___3227711_10	C___3227711_10_V	C___3227711_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4971536	"CAAAGAAGGGATCCTCAACTAACTA[C/T]GGGCAAAATAGATGGCCTCTCCCGT"	95.000	C___3227711_10	UNKNOWN	464.252	4,792.832	857.029	98.879	Homozygous C___3227711_10_M/C___3227711_10_M	Auto	40	0.996	1.450	0.392	0.982	24.586	23.731	N	N	N	N
-33	A1e1	false	V0001P001C01	C__11821218_10	C__11821218_10_V	C__11821218_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4855056	"TAATTAGGATTATGTGTACTTGCCT[A/G]TGAGTTCTCAAATAGCTAATGATAC"	95.000	C__11821218_10	UNKNOWN	1,632.064	1,510.517	838.676	98.898	Heterozygous C__11821218_10_V/C__11821218_10_M	Auto	40	1.097	1.066	0.446	0.484	23.435	23.569	N	N	N	N
-34	A1e2	false	V0001P001C01	C___1083232_10	C___1083232_10_V	C___1083232_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2032598	"TAATGGCCAGCAATTTAGTATTGCC[T/C]GACTTTTACTAATGCATGTGCTGTT"	95.000	C___1083232_10	UNKNOWN	286.011	500.054	827.471	77.307	Undetermined	Auto	40	0.596	0.700	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-35	A1e3	false	V0001P001C01	C___1007630_10	C___1007630_10_V	C___1007630_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs441460	"CATGGAAACAGAGCCTGGATCATAC[A/G]GGGAACTGCAACCATGATTGGATTA"	95.000	C___1007630_10	UNKNOWN	640.605	2,929.887	815.118	98.898	Homozygous C___1007630_10_M/C___1007630_10_M	Auto	40	0.888	1.330	0.443	0.795	22.120	21.851	N	N	N	N
-36	A1e4	false	V0001P001C01	C__15935210_10	C__15935210_10_V	C__15935210_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2259397	"AGTAGGGACCAAGGAAAGGTCATCT[C/T]GGACATAATCGGGAGCTGTGGCTAA"	95.000	C__15935210_10	UNKNOWN	3,879.001	2,410.567	784.059	98.886	Heterozygous C__15935210_10_V/C__15935210_10_M	Auto	40	1.375	1.170	0.708	0.525	21.488	22.031	N	N	N	N
-37	A1e5	false	V0001P001C01	C___1563023_10	C___1563023_10_V	C___1563023_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2136241	"GGCAAATGTAGCACATGTGAGGGTA[C/T]AAAATTATGATGCTACAATCAGAAA"	95.000	C___1563023_10	UNKNOWN	789.813	3,148.686	825.382	98.879	Homozygous C___1563023_10_M/C___1563023_10_M	Auto	40	0.976	1.389	0.344	0.932	21.849	21.347	N	N	N	N
-38	A1e6	false	V0001P001C01	C___1902433_10	C___1902433_10_V	C___1902433_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10771010	"GAGTAAGGAAGGGTATACTAAGGAA[C/T]AGAGTCACCGGGAGGAGCAACTTCA"	95.000	C___1902433_10	UNKNOWN	-91.633	4,123.588	815.882	98.898	Homozygous C___1902433_10_M/C___1902433_10_M	Auto	40	0.441	1.378	0.000	0.758	Undetermined	22.493	N	Y	Y	N
-39	A1e7	false	V0001P001C01	C__11710129_10	C__11710129_10_V	C__11710129_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs11083515	"ACACCAACAATTTGCTGTTATCCAC[A/G]GTCACTCAGCTAAACCTATGCCCTG"	95.000	C__11710129_10	UNKNOWN	3,396.146	2,647.173	836.471	98.898	Heterozygous C__11710129_10_V/C__11710129_10_M	Auto	40	1.332	1.196	0.596	0.541	22.409	22.366	N	N	N	N
-40	A1e8	false	V0001P001C01	C___1027548_20	C___1027548_20_V	C___1027548_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs768983	"CCATACTATAAACGAACTGTGAGTA[T/C]GCTCCACCAATTCCAAACAAACGTT"	95.000	C___1027548_20	UNKNOWN	107.831	405.235	807.647	88.277	Undetermined	Auto	40	0.686	0.884	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-41	A1f1	false	V0001P001C01	C___1250735_20	C___1250735_20_V	C___1250735_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4751955	"CTGGTCTTCAGCTTCCCTTTTCAGA[A/G]TGGAATGCACACGGTAAGTTTGTGA"	95.000	C___1250735_20	UNKNOWN	2,862.330	4,475.204	865.765	98.898	Heterozygous C___1250735_20_V/C___1250735_20_M	Auto	40	1.260	1.395	0.507	0.742	20.795	20.858	N	N	N	N
-42	A1f2	false	V0001P001C01	C__11522992_10	C__11522992_10_V	C__11522992_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6598531	"GCTTTGCAAATTCAAGTGAAAGCTC[G/T]GCAGAAACACAAGCGGGGAAGCCCT"	95.000	C__11522992_10	UNKNOWN	1,368.138	3,802.691	817.294	98.898	Homozygous C__11522992_10_M/C__11522992_10_M	Auto	40	1.097	1.360	0.454	0.696	24.517	24.063	N	N	N	N
-43	A1f3	false	V0001P001C01	C___1122315_10	C___1122315_10_V	C___1122315_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs11660213	"TGTCTAGAGCTGTATGCTTTCATGT[A/G]GTAGCCAGTAGCCACATGTGGCTAT"	95.000	C___1122315_10	UNKNOWN	4,742.033	3,578.874	817.971	98.886	Heterozygous C___1122315_10_V/C___1122315_10_M	Auto	40	1.388	1.280	0.610	0.533	21.246	20.805	N	N	N	N
-44	A1f4	false	V0001P001C01	C___1801627_20	C___1801627_20_V	C___1801627_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10869955	"AAACCAGCAGAACAGTAACATAATC[A/C]TGGGAGTGACATCCCTTCACTCTTG"	95.000	C___1801627_20	UNKNOWN	5,104.149	2,923.351	804.647	98.898	Homozygous C___1801627_20_V/C___1801627_20_V	Auto	40	1.516	1.203	0.967	0.404	21.250	21.027	N	N	N	N
-45	A1f5	false	V0001P001C01	C__10076371_10	C__10076371_10_V	C__10076371_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4783229	"GGGTCTTCCTCAACATAGCACATAA[C/T]CTGGCTTTCTAGATATTTTCCTTAA"	95.000	C__10076371_10	UNKNOWN	5,300.292	4,971.917	841.647	98.886	Heterozygous C__10076371_10_V/C__10076371_10_M	Auto	40	1.559	1.459	0.975	0.933	21.875	21.864	N	N	N	N
-46	A1f6	false	V0001P001C01	C___1670459_10	C___1670459_10_V	C___1670459_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6554653	"CATGAAACCACCCGGCAAATACTTA[C/T]ACATAGACTGATTTAGAGTGGAAAA"	95.000	C___1670459_10	UNKNOWN	4,406.018	3,051.817	845.706	98.898	Heterozygous C___1670459_10_V/C___1670459_10_M	Auto	40	1.399	1.242	0.730	0.685	21.715	22.448	N	N	N	N
-47	A1f7	false	V0001P001C01	C__16205730_10	C__16205730_10_V	C__16205730_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs2336695	"TAACTGATGTAGTGAAATGTTGAGA[A/G]AATCAGATATTAATGGTTGGCTGAG"	95.000	C__16205730_10	UNKNOWN	4,891.253	4,564.681	867.059	98.898	Heterozygous C__16205730_10_V/C__16205730_10_M	Auto	40	1.444	1.393	0.673	0.718	22.710	22.566	N	N	N	N
-48	A1f8	false	V0001P001C01	C__27402849_10	C__27402849_10_V	C__27402849_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs6927758	"TCTGAAATAGTGCTTATTGCATCGA[C/T]CAAAAAGAAGTGAATGCTGGAGTGG"	95.000	C__27402849_10	UNKNOWN	1,074.975	3,701.309	853.147	98.879	Homozygous C__27402849_10_M/C__27402849_10_M	Auto	40	1.067	1.425	0.417	0.935	22.536	21.330	N	N	N	N
-49	A1g1	false	V0001P001C01	C___2728408_10	C___2728408_10_V	C___2728408_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3010325	"TTTTGTACCTAGGGTTGTCTGATAC[C/T]AAAGCTCAAGTTCATTCTACTTCGT"	95.000	C___2728408_10	UNKNOWN	597.050	4,751.075	864.235	98.898	Homozygous C___2728408_10_M/C___2728408_10_M	Auto	40	1.018	1.454	0.312	0.953	22.312	21.390	N	N	N	N
-50	A1g2	false	V0001P001C01	C__26546714_10	C__26546714_10_V	C__26546714_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7773994	"ATAATATGAAACACCATGCAATCAT[G/T]AAGAGATTTTAATGTATCTTATCAA"	95.000	C__26546714_10	UNKNOWN	3,375.979	1,600.646	821.294	98.838	Heterozygous C__26546714_10_V/C__26546714_10_M	Auto	40	1.274	1.103	0.536	0.478	22.427	21.644	N	N	N	N
-51	A1g3	false	V0001P001C01	C__26524789_10	C__26524789_10_V	C__26524789_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3742257	"CCCAGAGCTGTGCAGTGTAGTGCCC[C/T]GGGTCTAGGCAACAGCAGAAAGTGG"	95.000	C__26524789_10	UNKNOWN	1,909.544	2,368.155	830.735	98.898	Heterozygous C__26524789_10_V/C__26524789_10_M	Auto	40	1.155	1.170	0.494	0.478	21.543	21.478	N	N	N	N
-52	A1g4	false	V0001P001C01	C__31386842_10	C__31386842_10_V	C__31386842_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs12318959	"AAACATTTTCGGCATTGCTTATTGA[C/T]GATGGCAGGGAAAGTTGAAGTTTCC"	95.000	C__31386842_10	UNKNOWN	4,792.921	3,885.181	852.500	98.898	Heterozygous C__31386842_10_V/C__31386842_10_M	Auto	40	1.420	1.330	0.682	0.652	21.777	21.967	N	N	N	N
-53	A1g5	false	V0001P001C01	C__30044763_10	C__30044763_10_V	C__30044763_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs10194978	"AAGGATTAGATAAAACAAAATCCGG[A/G]CACTTGACATGCTCAGGGATCAAAT"	95.000	C__30044763_10	UNKNOWN	4,286.648	3,213.266	834.118	98.886	Heterozygous C__30044763_10_V/C__30044763_10_M	Auto	40	1.419	1.351	0.738	0.788	22.109	22.398	N	N	N	N
-54	A1g6	false	V0001P001C01	C__29619553_10	C__29619553_10_V	C__29619553_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs9396715	"TCTCCTAGAAATCTTGGCCAAAGCA[C/T]AACTGGGTAAAAATAAGGAAAATAT"	95.000	C__29619553_10	UNKNOWN	6,131.003	4,093.376	837.294	98.898	Heterozygous C__29619553_10_V/C__29619553_10_M	Auto	40	1.550	1.372	0.921	0.714	22.899	23.595	N	N	N	N
-55	A1g7	false	V0001P001C01	C___2953330_10	C___2953330_10_V	C___2953330_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7796391	"TATTCTGCAAAGCCATAGGGACAAA[A/G]CTACCCAAGACCATGGGAACCCACC"	95.000	C___2953330_10	UNKNOWN	2,988.263	5,410.118	845.971	98.898	Homozygous C___2953330_10_M/C___2953330_10_M	Auto	40	1.287	1.618	0.470	0.977	26.149	21.094	N	N	N	N
-56	A1g8	false	V0001P001C01	C___8938211_20	C___8938211_20_V	C___8938211_20_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs3913290	"AGAGCTCACAATCAAGCCTGGTGAC[T/C]TGAGACTAGCCCTTGTGCATTCATA"	95.000	C___8938211_20	UNKNOWN	129.949	-81.473	884.294	100.000	Undetermined	Auto	40	0.412	0.676	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
-57	A1h1	false	V0001P001C01	C___8924366_10	C___8924366_10_V	C___8924366_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1377935	"TAATAAAATATGTAAGCGCAAGATA[C/T]TATGCTGGGCACCAAATAGGACACC"	95.000	C___8924366_10	UNKNOWN	3,951.246	2,986.782	817.882	98.898	Heterozygous C___8924366_10_V/C___8924366_10_M	Auto	40	1.349	1.261	0.607	0.598	24.787	24.715	N	N	N	N
-58	A1h2	false	V0001P001C01	C___8850710_10	C___8850710_10_V	C___8850710_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1157213	"GTTCATCACAGGGAACAGTGGTCTT[C/T]CAATATCTTCCTAGTCAGGACCCAT"	95.000	C___8850710_10	UNKNOWN	2,308.116	1,821.997	821.941	98.898	Heterozygous C___8850710_10_V/C___8850710_10_M	Auto	40	1.179	1.082	0.545	0.407	21.650	21.015	N	N	N	N
-59	A1h3	false	V0001P001C01	C___7457509_10	C___7457509_10_V	C___7457509_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1567612	"ATGGTTCCAAATATTTTATGCGTAT[A/G]CTTTATCTCTTACTTGCAGTACATA"	95.000	C___7457509_10	UNKNOWN	4,407.470	4,127.951	866.500	98.892	Heterozygous C___7457509_10_V/C___7457509_10_M	Auto	40	1.511	1.449	0.981	0.940	21.574	21.827	N	N	N	N
-60	A1h4	false	V0001P001C01	C___7431888_10	C___7431888_10_V	C___7431888_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1533486	"TTGTCTCAACCATCCAGAAATCAGT[G/T]TATTGAATTGGTTGATTTGATGTTA"	95.000	C___7431888_10	UNKNOWN	5,475.837	327.218	892.941	98.898	Homozygous C___7431888_10_V/C___7431888_10_V	Auto	40	1.526	0.597	0.976	0.000	21.072	Undetermined	Y	N	N	Y
-61	A1h5	false	V0001P001C01	C___7421900_10	C___7421900_10_V	C___7421900_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs1415762	"TTATGCAGAATACTAAAAAATCTTC[C/T]GAATCTTCTATCCTGTGTCTATCTT"	95.000	C___7421900_10	UNKNOWN	5,200.576	5,466.343	837.324	98.898	Heterozygous C___7421900_10_V/C___7421900_10_M	Auto	40	1.505	1.528	0.879	0.988	22.077	21.937	N	N	N	N
-62	A1h6	false	V0001P001C01	C_____43852_10	C_____43852_10_V	C_____43852_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs946065	"TGAGTGAGGTGTAGTTCACTTCATC[A/C]GGTAGAATCCCAACAAGCTGAGATG"	95.000	C_____43852_10	UNKNOWN	5,945.144	5,152.079	878.824	98.898	Heterozygous C_____43852_10_V/C_____43852_10_M	Auto	40	1.473	1.423	0.823	0.768	21.354	21.632	N	N	N	N
-63	A1h7	false	V0001P001C01	C__33211212_10	C__33211212_10_V	C__33211212_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs7564899	"TATCCTGTAAAGAAGCATCATGCAA[A/G]GGACTTTAAGTCACAGTAATTTAAT"	95.000	C__33211212_10	UNKNOWN	5,326.211	765.152	1,136.677	98.898	Homozygous C__33211212_10_V/C__33211212_10_V	Auto	40	1.473	0.989	0.772	0.734	22.382	20.877	N	N	N	N
-64	A1h8	false	V0001P001C01	C___3227711_10	C___3227711_10_V	C___3227711_10_M	VIC-NFQ-MGB	FAM-NFQ-MGB	rs4971536	"CAAAGAAGGGATCCTCAACTAACTA[C/T]GGGCAAAATAGATGGCCTCTCCCGT"	95.000	C___3227711_10	UNKNOWN	850.935	6,072.930	1,161.088	98.879	Homozygous C___3227711_10_M/C___3227711_10_M	Auto	40	1.109	1.510	0.343	0.982	22.400	22.203	N	N	N	N
diff --git a/tests/assets/YOA15_QuantStudio 12K Flex_export.txt b/tests/assets/YOA15_QuantStudio 12K Flex_export.txt
new file mode 100644
index 0000000..cace5a8
--- /dev/null
+++ b/tests/assets/YOA15_QuantStudio 12K Flex_export.txt
@@ -0,0 +1,86 @@
+* Barcode = YOA15
+* Block Type = OpenArray Block
+* Chemistry = TAQMAN
+* Comment = NA
+* Date Created = 03-29-2018 13:13:29 PM BST
+* Date Modified = 03-29-2018 17:28:03 PM BST
+* Experiment File Name =
+* Experiment Name = YOA15
+* Experiment Run Start Time = 03-29-2018 13:43:38 PM BST
+* Experiment Run Stop Time = 03-29-2018 17:22:28 PM BST
+* Experiment Type = SNP Genotyping
+* Instrument Name = 285880963
+* Instrument Serial Number = 285880963
+* Instrument Type = QuantStudio 12K Flex
+* Passive Reference = 
+* Quantification Cycle Method = Crt
+* Signal Smoothing On = true
+* Stage/ Cycle where Analysis is performed = Stage 3, Step 3
+* User Name = NA
+
+[Results]
+Well	Well Position	Omit	Sample Name	Assay ID	Allele1 Name	Allele2 Name	Allele1 Dyes	Allele2 Dyes	NCBI SNP Reference	Context Sequence	Quality Value	SNP Assay Name	Task	Allele1 R	Allele2 R	ROX Signal	Quality(%)	Call	Method	Call Cycle	Allele1 Amp Score	Allele2 Amp Score	Allele1 Cq Conf	Allele2 Cq Conf	Allele 1 Crt	Allele 2 Crt	ALLELE2CRTNOISE	ALLELE1CRTNOISE	ALLELE1CRTAMPLITUDE	ALLELE2CRTAMPLITUDE
+1	A1a1	false	V0001P001A01	C___7457509_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7457509_10	UNKNOWN	198.664	40.499	552.000	100.000	Undetermined	Auto	40	0.855	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+2	A1a2	false	V0001P001A01	C___8850710_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___8850710_10	UNKNOWN	525.829	205.242	529.471	100.000	Undetermined	Auto	40	1.106	0.572	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+3	A1a3	false	V0001P001A01	C___2728408_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___2728408_10	UNKNOWN	321.178	142.015	550.471	100.000	Undetermined	Auto	40	0.582	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+4	A1a4	false	V0001P001A01	C___1801627_20	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1801627_20	UNKNOWN	48.855	83.225	573.206	100.000	Undetermined	Auto	40	0.000	0.217	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+5	A1a5	false	V0001P001A01	C___1902433_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1902433_10	UNKNOWN	182.043	947.563	605.618	100.000	Undetermined	Auto	40	0.665	1.286	0.000	0.892	Undetermined	44.408	N	Y	Y	N
+6	A1a6	false	V0001P001A01	C__11710129_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__11710129_10	UNKNOWN	1,292.521	113.839	579.353	98.732	Homozygous Allele 1/Allele 1	Auto	40	1.276	0.000	0.927	0.000	39.021	Undetermined	Y	N	N	Y
+7	A1a7	false	V0001P001A01	C___1250735_20	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1250735_20	UNKNOWN	425.446	4,102.513	774.529	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.878	1.472	0.525	0.956	31.130	30.640	N	N	N	N
+8	A1a8	false	V0001P001A01	ANEPTUW	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANEPTUW	UNKNOWN	501.006	4,043.622	778.412	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.932	1.452	0.000	0.983	Undetermined	31.330	N	Y	N	N
+9	A1b1	false	V0001P001A01	C___7421900_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7421900_10	UNKNOWN	118.855	370.462	504.794	100.000	Undetermined	Auto	40	0.746	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+10	A1b2	false	V0001P001A01	C___1007630_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1007630_10	UNKNOWN	248.688	239.377	557.824	100.000	Undetermined	Auto	40	0.000	0.825	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+11	A1b3	false	V0001P001A01	C___1670459_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1670459_10	UNKNOWN	527.680	1,025.728	576.559	100.000	Undetermined	Auto	40	0.689	1.255	0.000	0.937	Undetermined	44.089	N	Y	Y	N
+12	A1b4	false	V0001P001A01	C_____43852_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C_____43852_10	UNKNOWN	2,861.802	2,801.190	611.353	98.892	Heterozygous Allele 1/Allele 2	Auto	40	1.288	1.316	0.677	0.802	31.538	31.478	N	N	N	N
+13	A1b5	false	V0001P001A01	C__30044763_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__30044763_10	UNKNOWN	657.668	2,728.557	618.824	98.898	Homozygous Allele 2/Allele 2	Auto	40	1.005	1.353	0.661	0.880	32.822	31.477	N	N	N	N
+14	A1b6	false	V0001P001A01	C__15935210_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__15935210_10	UNKNOWN	2,523.050	847.968	584.353	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.297	0.938	0.758	0.512	32.334	31.281	N	N	N	N
+15	A1b7	false	V0001P001A01	ANGZFYR	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANGZFYR	UNKNOWN	470.446	4,425.145	599.059	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.891	1.470	0.406	0.909	27.515	25.823	N	N	N	N
+16	A1b8	false	V0001P001A01	ANKA34K	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANKA34K	UNKNOWN	448.738	4,349.168	566.588	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.905	1.459	0.453	0.903	29.364	27.026	N	N	N	N
+17	A1c1	false	V0001P001A01	C__29619553_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__29619553_10	UNKNOWN	208.281	254.803	594.059	100.000	Undetermined	Manual	40	0.622	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+18	A1c2	false	V0001P001A01	C___1122315_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1122315_10	UNKNOWN	222.025	303.971	618.794	100.000	Undetermined	Auto	40	0.673	0.790	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+19	A1c3	false	V0001P001A01	C__16205730_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__16205730_10	UNKNOWN	233.670	390.890	569.235	100.000	Undetermined	Manual	40	0.391	0.448	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+20	A1c4	false	V0001P001A01	C___7431888_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7431888_10	UNKNOWN	819.978	253.656	592.941	100.000	Undetermined	Auto	40	1.235	0.927	0.000	0.000	Undetermined	Undetermined	Y	Y	N	Y
+21	A1c5	false	V0001P001A01	C__26524789_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__26524789_10	UNKNOWN	188.906	1,205.846	605.676	98.803	Homozygous Allele 2/Allele 2	Auto	40	0.000	1.171	0.000	0.813	Undetermined	35.466	N	Y	Y	N
+22	A1c6	false	V0001P001A01	C__31386842_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__31386842_10	UNKNOWN	1,121.544	530.678	595.088	100.000	Undetermined	Auto	40	1.363	1.080	0.882	0.000	46.171	Undetermined	Y	N	N	Y
+23	A1c7	false	V0001P001A01	C__10076371_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__10076371_10	UNKNOWN	-65.116	1,576.137	609.206	100.000	Undetermined	Auto	40	0.859	1.453	0.000	0.935	Undetermined	46.050	N	Y	Y	N
+24	A1c8	false	V0001P001A01	ANFVMEU	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANFVMEU	UNKNOWN	-50.267	1,154.415	600.882	100.000	Undetermined	Auto	40	0.629	1.358	0.000	0.942	Undetermined	46.708	N	Y	Y	N
+25	A1d1	false	V0001P001A01	C___2953330_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___2953330_10	UNKNOWN	199.458	85.860	590.324	100.000	Undetermined	Auto	40	0.000	0.412	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+26	A1d2	false	V0001P001A01	C__26546714_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__26546714_10	UNKNOWN	46.878	169.964	571.353	100.000	Undetermined	Manual	40	0.661	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+27	A1d3	false	V0001P001A01	C__11522992_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__11522992_10	UNKNOWN	67.143	78.577	579.647	100.000	Undetermined	Manual	40	0.515	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+28	A1d4	false	V0001P001A01	C___8924366_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___8924366_10	UNKNOWN	196.249	60.881	606.206	100.000	Undetermined	Auto	40	0.530	0.727	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+29	A1d5	false	V0001P001A01	C___1563023_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1563023_10	UNKNOWN	231.759	-68.010	600.794	100.000	Undetermined	Auto	40	0.989	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+30	A1d6	false	V0001P001A01	C__33211212_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__33211212_10	UNKNOWN	158.948	119.649	596.706	100.000	Undetermined	Auto	40	0.549	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+31	A1d7	false	V0001P001A01	ANH6AJN	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANH6AJN	UNKNOWN	-165.328	1,108.542	612.588	31.502	Undetermined	Auto	40	0.819	1.376	0.000	0.923	Undetermined	47.045	N	Y	Y	N
+32	A1d8	false	V0001P001A01	ANMFXPH	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANMFXPH	UNKNOWN	-171.169	619.186	606.941	0.000	Undetermined	Auto	40	0.199	1.252	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	N
+33	A1e1	false	V0001P001C01	C___7457509_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7457509_10	UNKNOWN	2,995.182	2,293.730	573.088	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.425	1.366	0.953	0.975	34.299	34.083	N	N	N	N
+34	A1e2	false	V0001P001C01	C___8850710_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___8850710_10	UNKNOWN	1,332.932	911.900	592.853	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.089	0.925	0.500	0.348	22.882	21.777	N	N	N	N
+35	A1e3	false	V0001P001C01	C___2728408_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___2728408_10	UNKNOWN	2,969.196	1,965.910	573.882	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.365	1.205	0.743	0.631	27.022	27.167	N	N	N	N
+36	A1e4	false	V0001P001C01	C___1801627_20	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1801627_20	UNKNOWN	1,751.393	3,100.312	552.294	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.256	1.376	0.715	0.847	31.384	31.341	N	N	N	N
+37	A1e5	false	V0001P001C01	C___1902433_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1902433_10	UNKNOWN	47.150	2,582.138	580.500	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.000	1.259	0.000	0.647	Undetermined	23.603	N	Y	Y	N
+38	A1e6	false	V0001P001C01	C__11710129_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__11710129_10	UNKNOWN	296.821	1,966.857	579.971	98.892	Homozygous Allele 2/Allele 2	Auto	40	0.000	1.181	0.000	0.488	Undetermined	22.391	N	Y	Y	N
+39	A1e7	false	V0001P001C01	C___1250735_20	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1250735_20	UNKNOWN	2,036.477	2,893.441	580.353	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.218	1.317	0.550	0.654	21.581	21.701	N	N	N	N
+40	A1e8	false	V0001P001C01	ANEPTUW	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANEPTUW	UNKNOWN	2,170.768	3,416.340	571.765	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.244	1.361	0.559	0.705	21.803	22.006	N	N	N	N
+41	A1f1	false	V0001P001C01	C___7421900_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7421900_10	UNKNOWN	3,849.263	1,264.666	587.118	98.898	Homozygous Allele 1/Allele 1	Auto	40	1.467	1.058	0.933	0.000	31.643	Undetermined	Y	N	N	N
+42	A1f2	false	V0001P001C01	C___1007630_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1007630_10	UNKNOWN	1,958.177	838.665	596.382	98.484	Heterozygous Allele 1/Allele 2	Auto	40	1.258	1.052	0.688	0.000	32.947	Undetermined	Y	N	N	N
+43	A1f3	false	V0001P001C01	C___1670459_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1670459_10	UNKNOWN	2,712.877	1,386.837	629.853	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.317	1.127	0.732	0.697	30.828	31.144	N	N	N	N
+44	A1f4	false	V0001P001C01	C_____43852_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C_____43852_10	UNKNOWN	1,267.726	4,125.392	574.029	98.892	Homozygous Allele 2/Allele 2	Auto	40	1.048	1.380	0.323	0.710	23.974	23.500	N	N	N	N
+45	A1f5	false	V0001P001C01	C__30044763_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__30044763_10	UNKNOWN	3,287.821	2,279.942	600.324	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.330	1.228	0.631	0.578	24.222	24.180	N	N	N	N
+46	A1f6	false	V0001P001C01	C__15935210_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__15935210_10	UNKNOWN	303.189	2,047.390	575.794	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.816	1.158	0.000	0.488	Undetermined	24.170	N	Y	Y	N
+47	A1f7	false	V0001P001C01	ANGZFYR	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANGZFYR	UNKNOWN	2,246.758	3,304.016	614.529	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.278	1.336	0.597	0.676	21.999	22.069	N	N	N	N
+48	A1f8	false	V0001P001C01	ANKA34K	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANKA34K	UNKNOWN	2,273.130	3,753.003	608.059	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.268	1.362	0.572	0.648	21.819	21.673	N	N	N	N
+49	A1g1	false	V0001P001C01	C__29619553_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__29619553_10	UNKNOWN	549.232	131.843	679.324	100.000	Undetermined	Manual	40	1.134	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+50	A1g2	false	V0001P001C01	C___1122315_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1122315_10	UNKNOWN	2,456.731	1,545.291	646.824	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.350	1.227	0.931	0.893	38.273	37.085	N	N	N	N
+51	A1g3	false	V0001P001C01	C__16205730_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__16205730_10	UNKNOWN	977.069	830.564	603.324	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.322	1.148	0.814	0.000	46.502	Undetermined	Y	N	N	N
+52	A1g4	false	V0001P001C01	C___7431888_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___7431888_10	UNKNOWN	2,872.738	818.201	636.147	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.397	1.088	0.955	0.869	38.179	37.997	N	N	N	N
+53	A1g5	false	V0001P001C01	C__26524789_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__26524789_10	UNKNOWN	142.903	1,742.205	646.382	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.661	1.216	0.000	0.646	Undetermined	29.670	N	Y	Y	N
+54	A1g6	false	V0001P001C01	C__31386842_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__31386842_10	UNKNOWN	280.437	2,464.494	633.265	98.898	Homozygous Allele 2/Allele 2	Auto	40	0.883	1.369	0.000	0.966	Undetermined	37.552	N	Y	Y	N
+55	A1g7	false	V0001P001C01	C__10076371_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__10076371_10	UNKNOWN	3,572.756	1,960.236	597.853	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.506	1.305	0.970	0.907	37.792	37.796	N	N	N	N
+56	A1g8	false	V0001P001C01	ANFVMEU	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANFVMEU	UNKNOWN	3,935.214	2,089.143	653.824	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.544	1.318	0.965	0.872	38.227	37.395	N	N	N	N
+57	A1h1	false	V0001P001C01	C___2953330_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___2953330_10	UNKNOWN	404.453	2,625.771	792.265	98.892	Homozygous Allele 2/Allele 2	Auto	40	0.000	1.556	0.000	0.952	Undetermined	45.556	N	Y	Y	N
+58	A1h2	false	V0001P001C01	C__26546714_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__26546714_10	UNKNOWN	141.880	295.943	772.206	100.000	Undetermined	Manual	40	0.559	0.382	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+59	A1h3	false	V0001P001C01	C__11522992_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__11522992_10	UNKNOWN	264.144	380.722	796.735	100.000	Undetermined	Manual	40	0.825	0.000	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	Y
+60	A1h4	false	V0001P001C01	C___8924366_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___8924366_10	UNKNOWN	648.600	423.215	754.059	100.000	Undetermined	Manual	40	1.225	1.026	0.817	0.000	46.999	Undetermined	Y	N	N	Y
+61	A1h5	false	V0001P001C01	C___1563023_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C___1563023_10	UNKNOWN	1,488.499	283.037	736.000	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.360	0.988	0.939	0.000	41.327	Undetermined	Y	N	N	Y
+62	A1h6	false	V0001P001C01	C__33211212_10	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	C__33211212_10	UNKNOWN	449.108	461.986	737.088	98.898	Heterozygous Allele 1/Allele 2	Auto	40	1.139	1.124	0.000	0.000	Undetermined	Undetermined	Y	Y	Y	N
+63	A1h7	false	V0001P001C01	ANH6AJN	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANH6AJN	UNKNOWN	5,709.859	3,794.156	947.294	100.000	Heterozygous Allele 1/Allele 2	Manual	40	1.562	1.405	0.971	0.899	35.322	35.225	N	N	N	N
+64	A1h8	false	V0001P001C01	ANMFXPH	Allele 1	Allele 2	VIC-NFQ-MGB	FAM-NFQ-MGB			95.000	ANMFXPH	UNKNOWN	6,027.021	3,848.086	995.176	98.798	Heterozygous Allele 1/Allele 2	Auto	40	1.595	1.399	0.970	0.868	35.221	35.093	N	N	N	N
diff --git a/tests/test_common.py b/tests/test_common.py
index 43d6423..d50500e 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -41,7 +41,7 @@ class TestCommon(TestCase):
     assets = join(dirname(abspath(__file__)), 'assets')
     etc_path = join(abspath(dirname(EPPs.__file__)), 'etc')
     genotype_csv = join(assets, 'E03159_WGS_32_panel_9504430.csv')
-    genotype_quantStudio = join(assets, 'VGA55_QuantStudio 12K Flex_export.txt')
+    genotype_quantStudio = join(assets, 'YOA15_QuantStudio 12K Flex_export.txt')
     accufill_log = join(assets, 'OpenArrayLoad_Log.csv')
     small_reference_fai = join(assets, 'genotype_32_SNPs_genome_600bp.fa.fai')
     reference_fai = join(assets, 'GRCh37.fa.fai')
diff --git a/tests/test_convert_and_dispatch_genotypes.py b/tests/test_convert_and_dispatch_genotypes.py
index 177010d..3d34ed3 100644
--- a/tests/test_convert_and_dispatch_genotypes.py
+++ b/tests/test_convert_and_dispatch_genotypes.py
@@ -21,19 +21,19 @@ class TestGenotypeConversion(TestCommon):
 
     def setUp(self):
         self.geno_conversion = GenotypeConversion(
-            open_files([self.genotype_csv]), self.accufill_log, 'igmm',  self.small_reference_fai, flank_length=600
+            open_files([self.genotype_quantStudio]), self.small_reference_fai, flank_length=600
         )
 
     def test_generate_vcf(self):
         # header_lines = ['##header line1', '##header line2']
         # snp_ids = ['id4', 'id2', 'id3', 'id1']
         # TODO: make assertions on what header lines, snp IDs, etc. have been written
-        sample_id = '9504430'
+        sample_id = 'V0001P001C01'
         path = join(self.assets, 'test_generate')
         vcf_file = path + '.vcf'
         assert self.geno_conversion.generate_vcf(sample_id, new_name=path) == vcf_file
         with open(vcf_file) as f:
-            assert len([l for l in f.readlines() if not l.startswith('#')]) == 32
+            assert 26 == len([l for l in f.readlines() if not l.startswith('#')])
 
     def test_get_genotype_from_call(self):
         genotype = self.geno_conversion.get_genotype_from_call('A', 'T', 'Both', )
@@ -71,15 +71,9 @@ class TestGenotypeConversion(TestCommon):
         ]
         assert refence_length == expected_ref_length
 
-    def test_init_genotype_csv(self):
-        assert self.geno_conversion.sample_names == {'9504430'}
-        assert len(self.geno_conversion.all_records) == 32
-
     def test_parse_QuantStudio_AIF_genotype(self):
-        geno_conversion = GenotypeConversion(open_files([self.genotype_quantStudio]), open(self.accufill_log),
-                                             'quantStudio', self.small_reference_fai, flank_length=600)
-        assert geno_conversion.sample_names == {'V0001P001C01', 'V0001P001A01'}
-        assert len(geno_conversion.all_records) == 32
+        assert self.geno_conversion.sample_names == {'V0001P001C01', 'V0001P001A01'}
+        assert len(self.geno_conversion.all_records) == 26
 
     def test_find_field(self):
         observed_fieldnames = ('__this__', 'that', 'OTHER')
@@ -100,33 +94,97 @@ class TestUploadVcfToSamples(TestEPP):
             'a_user',
             'a_password',
             self.log_file,
-            mode='igmm',
-            input_genotypes_files=[self.genotype_csv]
+            input_genotypes_files=[self.genotype_quantStudio]
         )
-
+        self.lims_sample1 = FakeEntity(name='V0001P001A01', udf={}, put=Mock())
+        self.lims_sample2 = FakeEntity(name='V0001P001C01', udf={}, put=Mock())
         fake_all_inputs = Mock(
             return_value=[
-                Mock(samples=[FakeEntity(name='this', udf={'User Sample Name': '9504430'}, put=Mock())])
+                Mock(samples=[self.lims_sample1]),
+                Mock(samples=[self.lims_sample2])
             ]
         )
+        # all output artifacts
+        self.outputs = {}
+
+        def fake_find_output_art(inart):
+            if inart.samples[0] not in self.outputs:
+                self.outputs[inart.samples[0]]= Mock(samples=inart.samples, udf={}, put=Mock())
+            return [self.outputs[inart.samples[0]]]
+
         self.patched_process = patch.object(StepEPP, 'process', new_callable=PropertyMock(
             return_value=Mock(all_inputs=fake_all_inputs)
         ))
+        self.patched_find_output_art = patch.object(UploadVcfToSamples, '_find_output_art',
+                                                    side_effect=fake_find_output_art)
 
-    def test_upload(self):
+    def test_upload_first_time(self):
         patched_log = patch('scripts.convert_and_dispatch_genotypes.UploadVcfToSamples.info')
-        patched_generate_vcf = patch('scripts.convert_and_dispatch_genotypes.GenotypeConversion.generate_vcf')
+        patched_generate_vcf = patch('scripts.convert_and_dispatch_genotypes.GenotypeConversion.generate_vcf', return_value='uploaded_file')
         patched_remove = patch('scripts.convert_and_dispatch_genotypes.remove')
 
         exp_log_msgs = (
-            ('Matching against %s artifacts', 1),
-            ('Matching %s against user sample name %s', 'this', '9504430'),
-            ('Matched and uploaded %s artifacts against %s genotype results', 1, 1),
+            ('Matching %s sample from file against %s artifacts', 2, 2),
+            ('Matching V0001P001A01',),
+            ('Matching V0001P001C01',),
+            ('Matched and uploaded %s artifacts against %s genotype results', 2, 2),
             ('%s artifacts did not match', 0),
             ('%s genotyping results were not used', 0)
         )
 
-        with patched_log as p, patched_generate_vcf, patched_remove, self.patched_lims, self.patched_process:
+        with patched_log as p, patched_generate_vcf, patched_remove, self.patched_lims as mlims, self.patched_process,\
+                self.patched_find_output_art:
+            mlims.upload_new_file.return_value = Mock(id='file_id')
             self.epp._run()
+
             for m in exp_log_msgs:
                 p.assert_any_call(*m)
+            mlims.upload_new_file.assert_any_call(self.lims_sample1, 'uploaded_file')
+            mlims.upload_new_file.assert_called_with(self.lims_sample2, 'uploaded_file')
+            self.lims_sample1.put.assert_called_once_with()
+            self.lims_sample2.put.assert_called_once_with()
+            assert self.lims_sample1.udf == {
+                'QuantStudio Data Import Completed #': 1,
+                'Number of Calls (Best Run)': 6,
+                'Genotyping results file id': 'file_id'
+            }
+            assert self.outputs[self.lims_sample1].udf == {'Number of Calls (This Run)': 6}
+            assert self.lims_sample2.udf == {
+                'QuantStudio Data Import Completed #': 1,
+                'Number of Calls (Best Run)': 22,
+                'Genotyping results file id': 'file_id'
+            }
+            assert self.outputs[self.lims_sample2].udf == {'Number of Calls (This Run)': 22}
+
+    def test_upload_second_time(self):
+        patched_log = patch('scripts.convert_and_dispatch_genotypes.UploadVcfToSamples.info')
+        patched_generate_vcf = patch('scripts.convert_and_dispatch_genotypes.GenotypeConversion.generate_vcf', return_value='uploaded_file')
+        patched_remove = patch('scripts.convert_and_dispatch_genotypes.remove')
+
+        with patched_log as p, patched_generate_vcf, patched_remove, self.patched_lims as mlims, self.patched_process, \
+                self.patched_find_output_art:
+            self.lims_sample1.udf = {
+                'QuantStudio Data Import Completed #': 1,
+                'Number of Calls (Best Run)': 12,
+                'Genotyping results file id': 'old_file_id'
+            }
+            self.lims_sample2.udf = {
+                'QuantStudio Data Import Completed #': 1,
+                'Number of Calls (Best Run)': 12,
+                'Genotyping results file id': 'old_file_id'
+            }
+            mlims.upload_new_file.return_value = Mock(id='file_id')
+            self.epp._run()
+            assert self.lims_sample1.udf == {
+                'QuantStudio Data Import Completed #': 2,
+                'Number of Calls (Best Run)': 12,
+                'Genotyping results file id': 'old_file_id'
+            }
+            assert self.outputs[self.lims_sample1].udf == {'Number of Calls (This Run)': 6}
+
+            assert self.lims_sample2.udf == {
+                'QuantStudio Data Import Completed #': 2,
+                'Number of Calls (Best Run)': 22,
+                'Genotyping results file id': 'file_id'
+            }
+            assert self.outputs[self.lims_sample2].udf == {'Number of Calls (This Run)': 22}
diff --git a/tests/test_populate_review_step.py b/tests/test_populate_review_step.py
index 6e6c8e0..d1b2eea 100644
--- a/tests/test_populate_review_step.py
+++ b/tests/test_populate_review_step.py
@@ -1,7 +1,7 @@
 from pyclarity_lims.entities import Artifact
 from scripts import populate_review_step as p
 from tests.test_common import TestEPP, NamedMock
-from unittest.mock import Mock, patch, PropertyMock, call
+from unittest.mock import Mock, patch, PropertyMock
 
 
 class TestPopulator(TestEPP):
@@ -13,7 +13,7 @@ class TestPopulator(TestEPP):
             self.epp_cls,
             'samples',
             new_callable=PropertyMock(
-                return_value=[NamedMock(real_name='a_sample', udf={'Required Yield (Gb)': 95, 'Coverage (X)': 30})]
+                return_value=[NamedMock(real_name='a_sample', udf={'Yield for Quoted Coverage (Gb)': 95})]
             )
         )
         self.patched_lims = patch.object(self.epp_cls, 'lims', new_callable=PropertyMock)
@@ -30,49 +30,37 @@ class TestPopulator(TestEPP):
 class TestPullRunElementInfo(TestPopulator):
     epp_cls = p.PullRunElementInfo
     fake_rest_entity = {
-        'aggregated': {'clean_yield_in_gb': 20,
-                       'clean_yield_q30_in_gb': 15,
-                       'clean_pc_q30': 75,
-                       'pc_adaptor': 1.2},
         'run_element_id': 'id',
         'passing_filter_reads': 120000000,
+        'clean_yield_in_gb': 20,
+        'clean_yield_q30_in_gb': 15,
+        'clean_pc_q30': 75,
         'lane_pc_optical_dups': 10,
+        'pc_adapter': 1.2,
         'reviewed': 'pass',
         'review_comments': 'alright',
-        'review_date': '12_02_2107_12:43:24',
+        'review_date': '12_02_2107_12:43:24'
     }
     expected_udfs = {
         'RE Id': 'id',
         'RE Nb Reads': 120000000,
         'RE Yield': 20,
         'RE Yield Q30': 15,
-        'RE Coverage': 34.2,
         'RE %Q30': 75,
         'RE Estimated Duplicate Rate': 10,
         'RE %Adapter': 1.2,
         'RE Review status': 'pass',
         'RE Review Comment': 'alright',
-        'RE Review date': '2107-02-12',
-        'RE Useable': 'yes',
-        'RE Useable Comment': 'AR: Good yield'
+        'RE Review date': '2107-02-12'
     }
 
     def test_pull(self):
-
-        patched_output_artifacts_per_sample = patch.object(
-            self.epp_cls,
-            'output_artifacts_per_sample',
-            return_value=[Mock(spec=Artifact, udf={'RE Coverage': 34.2}, samples=[NamedMock(real_name='a_sample')])]
-        )
-
         with self.patched_lims as pl, self.patched_samples, self.patched_get_docs as pg, \
-                patched_output_artifacts_per_sample as poa:
+                self.patched_output_artifacts_per_sample as poa:
             self.epp.run()
 
-            assert pg.call_count == 3
-            assert pg.call_args_list == [call('run_elements', where={'sample_id': 'a_sample'}),
-                                         call('samples', where={'sample_id': 'a_sample'}),
-                                         call('samples', where={'sample_id': 'a_sample'})]
+            assert pg.call_count == 1
+            pg.assert_called_with(self.epp.endpoint, match={'sample_id': 'a_sample'})
 
             # Check that the udfs have been added
             assert dict(poa.return_value[0].udf) == self.expected_udfs
@@ -84,16 +72,16 @@ class TestPullRunElementInfo(TestPopulator):
         def patch_output_artifact(output_artifacts):
             return patch.object(self.epp_cls, 'output_artifacts_per_sample', return_value=output_artifacts)
 
-        sample = NamedMock(real_name='a_sample', udf={'Required Yield (Gb)': 95, 'Coverage (X)': 30})
+        sample = NamedMock(real_name='a_sample', udf={'Yield for Quoted Coverage (Gb)': 95})
         patched_output_artifacts_per_sample = patch_output_artifact([
-            Mock(spec=Artifact, udf={'RE Yield': 115, 'RE %Q30': 75, 'RE Review status': 'pass', 'RE Coverage': 35.2}),
-            Mock(spec=Artifact, udf={'RE Yield': 95, 'RE %Q30': 85, 'RE Review status': 'pass', 'RE Coverage': 36.7}),
-            Mock(spec=Artifact, udf={'RE Yield': 15, 'RE %Q30': 70, 'RE Review status': 'fail', 'RE Coverage': 34.1}),
+            Mock(spec=Artifact, udf={'RE Yield Q30': 115, 'RE %Q30': 75, 'RE Review status': 'pass'}),
+            Mock(spec=Artifact, udf={'RE Yield Q30': 95, 'RE %Q30': 85, 'RE Review status': 'pass'}),
+            Mock(spec=Artifact, udf={'RE Yield Q30': 15, 'RE %Q30': 70, 'RE Review status': 'fail'}),
         ])
-        with patched_output_artifacts_per_sample as poa, self.patched_get_docs as pg:
+        with patched_output_artifacts_per_sample as poa:
             self.epp.assess_sample(sample)
             assert poa.return_value[0].udf['RE Useable'] == 'no'
-            assert poa.return_value[0].udf['RE Useable Comment'] == 'AR: Too much good yield'
+            assert poa.return_value[0].udf['RE Useable Comment'] == 'AR: To much good yield'
 
             assert poa.return_value[1].udf['RE Useable'] == 'yes'
             assert poa.return_value[1].udf['RE Useable Comment'] == 'AR: Good yield'
@@ -102,61 +90,38 @@ class TestPullRunElementInfo(TestPopulator):
             assert poa.return_value[2].udf['RE Useable Comment'] == 'AR: Failed and not needed'
 
         patched_output_artifacts_per_sample = patch_output_artifact([
-            Mock(spec=Artifact, udf={'RE Yield': 115, 'RE %Q30': 85, 'RE Review status': 'pass', 'RE Coverage': 35.2}),
-            Mock(spec=Artifact, udf={'RE Yield': 15, 'RE %Q30': 70, 'RE Review status': 'fail', 'RE Coverage': 33.6}),
+            Mock(spec=Artifact, udf={'RE Yield Q30': 115, 'RE %Q30': 85, 'RE Review status': 'pass'}),
+            Mock(spec=Artifact, udf={'RE Yield Q30': 15, 'RE %Q30': 70, 'RE Review status': 'fail'}),
         ])
-        with patched_output_artifacts_per_sample as poa, self.patched_get_docs as pg:
+        with patched_output_artifacts_per_sample as poa:
             self.epp.assess_sample(sample)
             assert poa.return_value[0].udf['RE Useable'] == 'yes'
             assert poa.return_value[0].udf['RE Useable Comment'] == 'AR: Good yield'
 
-
             assert poa.return_value[1].udf['RE Useable'] == 'no'
             assert poa.return_value[1].udf['RE Useable Comment'] == 'AR: Failed and not needed'
 
-        patched_output_artifacts_per_sample = patch_output_artifact([
-            Mock(spec=Artifact, udf={'RE Yield': 115, 'RE %Q30': 85, 'RE Review status': 'pass', 'RE Coverage': 35.2}),
-            Mock(spec=Artifact, udf={'RE Yield': 15, 'RE %Q30': 70, 'RE Review status': 'fail', 'RE Coverage': 33.6}),
-        ])
-
-        delivered = 'scripts.populate_review_step.PullRunElementInfo.delivered'
-        processed = 'scripts.populate_review_step.PullRunElementInfo.processed'
-        patched_delivered = patch(delivered, return_value=True)
-        pathed_processed = patch(processed, return_value=True)
-
-        with patched_output_artifacts_per_sample as poa, self.patched_get_docs as pg, patched_delivered:
-            self.epp.assess_sample(sample)
-            assert poa.return_value[0].udf['RE Useable'] == 'no'
-            assert poa.return_value[0].udf['RE Useable Comment'] == 'AR: Delivered'
-            assert poa.return_value[1].udf['RE Useable'] == 'no'
-            assert poa.return_value[1].udf['RE Useable Comment'] == 'AR: Delivered'
-
-        with patched_output_artifacts_per_sample as poa, self.patched_get_docs as pg, pathed_processed:
-            self.epp.assess_sample(sample)
-            assert poa.return_value[0].udf['RE Useable'] == 'no'
-            assert poa.return_value[0].udf['RE Useable Comment'] == 'AR: Sample already processed'
-            assert poa.return_value[1].udf['RE Useable'] == 'no'
-            assert poa.return_value[1].udf['RE Useable Comment'] == 'AR: Sample already processed'
-
     def test_field_from_entity(self):
         entity = {'this': {'that': 'other'}}
         assert self.epp.field_from_entity(entity, 'this.that') == 'other'
         assert entity == {'this': {'that': 'other'}}  # not changed
 
 
-class TestPullSampleInfo(TestPopulator):
+class TestPullSampleInfo(TestPullRunElementInfo):
     epp_cls = p.PullSampleInfo
     fake_rest_entity = {
         'sample_id': 'a_sample',
         'user_sample_id': 'a_user_sample_id',
         'clean_yield_in_gb': 5,
-        'aggregated': {'clean_pc_q30': 70,
-                       'pc_mapped_reads': 75,
-                       'pc_duplicate_reads': 5,
-                       'mean_coverage': 30,
-                       'gender_match': 'Match',
-                       'genotype_match': 'Match'},
-        'matching_species': ['Homo sapiens', 'Thingius thingy'],
+        'clean_pc_q30': 70,
+        'pc_mapped_reads': 75,
+        'pc_duplicate_reads': 5,
+        'coverage': {'mean': 30},
+        'species_contamination': {
+            'contaminant_unique_mapped': {'Homo sapiens': 70000, 'Thingius thingy': 501, 'Sus scrofa': 499}
+        },
+        'gender_match': 'Match',
+        'genotype_match': 'Match',
         'sample_contamination': {'freemix': 0.1},
         'reviewed': 'pass',
         'review_comments': 'alright',
@@ -197,7 +162,7 @@ class TestPullSampleInfo(TestPopulator):
             assert poa.return_value[1].udf['SR Useable Comments'] == 'AR: Review failed'
 
     def test_field_from_entity(self):
-        obs = self.epp.field_from_entity(self.fake_rest_entity, 'matching_species')
+        obs = self.epp.field_from_entity(self.fake_rest_entity, 'species_contamination')
         assert obs == 'Homo sapiens, Thingius thingy'