docs: def process_fish(self, limit=None):
          """
          Fish give identifiers to the "effective genotypes" that we create.
          We can match these by:
          Fish = (intrinsic) genotype + set of morpholinos
  
          We assume here that the intrinsic genotypes and their parts
          will be processed separately, prior to calling this function.
  
          :param limit:
          :return:
  
          """
  
          LOG.info("Processing Fish Parts")
  
          raw = '/'.join((self.rawdir, self.files['fish_components']['file']))
  
          if self.test_mode:
              graph = self.testgraph
          else:
              graph = self.graph
  
          model = Model(graph)
          taxon_id = self.globaltt['Danio rerio']
  
          geno = Genotype(graph)
  
          allele_to_construct_hash = {}
  
          with open(raw, 'r', encoding="utf8") as csvfile:
              filereader = csv.reader(csvfile, delimiter='\t', quotechar='\"')
              for row in filereader:
  
                  (fish_num, fish_name, gene_num, gene_symbol, affector_num,
                   affector_symbol, construct_num, construct_symbol,
                   background_num, background_symbol, genotype_num,
                   genotype_name
                   # , empty
                  ) = row
  
                  # fish have the following components:
                  #  *  genotype, which is the intrinsic genotype;
                  #     this may be a genetic background (WT)
                  #  *  an optional background for the intrinsic genotype
                  #  *  affectors == alleles or morphants
                  #  *  constructs which may give rise to the affectors
                  #  *  affected genes
  
                  if fish_num not in self.fish_parts:
                      self.fish_parts[fish_num] = {}
                      self.fish_parts[fish_num] = {
                          'intrinsic_genotype': genotype_num,
                          'affectors': set(),
                          'fish_label': fish_name
                      }
  
                  # HACK - bad allele id - replace it with the new one  FIXME
                  if affector_num == 'ZDB-ALT-090504-1':
                      affector_num = 'ZDB-ALT-040723-4'
  
                  self.fish_parts[fish_num]['affectors'].add(affector_num)
  
                  # add the constructs that the allele comes from
                  if construct_num != '':
                      if affector_num not in allele_to_construct_hash:
                          allele_to_construct_hash[affector_num] = set()
                      allele_to_construct_hash[affector_num].add(construct_num)
  
              # ### finish looping through fish file
  
          # given the components of a fish,
          # subtract out the intrinsic parts to just leave the extrinsic
          # to create the extrinsic genotypes.
          line_counter = 0
  
          for fish_num in self.fish_parts:
              if self.test_mode and fish_num not in self.test_ids['fish']:
                  continue
  
              line_counter += 1
              fish_id = 'ZFIN:'+fish_num
              fish = self.fish_parts[fish_num]
  
              # get the intrinsic parts
              intrinsic_genotype_num = fish['intrinsic_genotype']
              intrinsic_genotype_id = 'ZFIN:'+intrinsic_genotype_num
              intrinsic_genotype_label = self.id_label_map.get(
                  intrinsic_genotype_id)
              if intrinsic_genotype_num not in self.geno_alleles:
                  intrinsic_parts = set()
              else:
                  intrinsic_parts = self.geno_alleles[intrinsic_genotype_num]
  
              # subtract out the intrinsic parts, to get the extrinsic parts
              extrinsic_parts = fish['affectors'] - intrinsic_parts
              extrinsic_list = list(sorted(extrinsic_parts))
  
              # build up the extrinsic genotype from it's parts.
              # these will be reagents/morphants.
              if len(extrinsic_list) > 0:
                  list_of_targeted_genes = []
                  gene_to_reagent_hash = {}
                  for eid in extrinsic_list:
                      # link the morpholino to the genes that it affects
                      eid = 'ZFIN:' + eid
  
                      # just in case, skip over the ALTs
                      if re.search(r'ALT', eid):
                          continue
                      ag = self.variant_loci_genes.get(eid)
  
                      # LOG.debug("%s affected genes %s", eid, str(ag))
                      if ag is None:
                          pass
                          # LOG.warn("No affected genes for %s", eid)
                      else:
                          # turn the gene-targeting-reagents inside out,
                          # such that instead of morph -> set(genes)
                          # we make a gene -> set(morphs)
  
                          for gid in ag:
                              if gid not in gene_to_reagent_hash:
                                  gene_to_reagent_hash[gid] = set()
                              gene_to_reagent_hash[gid].add(eid)
                      # end loop through each extrinsic component
  
                  for gid in gene_to_reagent_hash:
                      reagent_list = sorted(list(gene_to_reagent_hash.get(gid)))
                      # create variant gene(s) that have been targeted
                      # by the reagent
  
                      if gid not in self.id_label_map:
                          # should not happen, except maybe in testing
                          LOG.error("%s not in id-label-hash", gid)
                          glabel = gid
                      else:
                          glabel = self.id_label_map[gid]
  
                      eid = '-'.join(reagent_list)
  
                      targeted_gene_id = self.make_targeted_gene_id(
                          gid, eid)
                      # get the reagent labels
                      elabel = ', '.join(
                          self.id_label_map.get(l) for l in reagent_list)
                      if elabel is None:
                          elabel = eid  # should not happen, but just in case
                      targeted_gene_label = glabel + '<' + elabel + '>'
  
                      for r in reagent_list:
                          geno.addReagentTargetedGene(r, gid, targeted_gene_id,
                                                      targeted_gene_label)
                      self.id_label_map[targeted_gene_id] = targeted_gene_label
                      list_of_targeted_genes += [targeted_gene_id]
                      # end loop through each gene that is targeted
                  list_of_targeted_genes = sorted(list_of_targeted_genes)
                  extrinsic_id = '_:'+re.sub(
                      r':?_?', '', '-'.join(list_of_targeted_genes))
                  extrinsic_label = '; '.join(
                      str(self.id_label_map.get(l))
                      for l in list_of_targeted_genes)
                  self.id_label_map[extrinsic_id] = extrinsic_label
  
                  # add the parts
                  for tg in list_of_targeted_genes:
                      if tg != extrinsic_id:
                          geno.addParts(
                              tg, extrinsic_id, self.globaltt['has_variant_part'])
  
              else:
                  extrinsic_id = None
                  extrinsic_label = None
              if extrinsic_id is not None:
                  geno.addGenotype(
                      extrinsic_id, extrinsic_label, self.globaltt['extrinsic_genotype'])
                  geno.addParts(
                      extrinsic_id, fish_id, self.globaltt['has_variant_part'])
  
              # check if the intrinsic is in the wildtype genotypes,
              # then it's a genomic background
              if intrinsic_genotype_id in self.wildtype_genotypes:
                  intrinsic_rel = self.globaltt['has_reference_part']
                  intrinsic_type = self.globaltt['genomic_background']
              else:
                  intrinsic_rel = self.globaltt['has_variant_part']
                  intrinsic_type = self.globaltt['intrinsic_genotype']
              geno.addGenotype(
                  intrinsic_genotype_id, intrinsic_genotype_label, intrinsic_type)
  
              # add the intrinsic to the fish
              geno.addParts(intrinsic_genotype_id, fish_id, intrinsic_rel)
  
              # build the fish label
              if extrinsic_id is None:
                  fish_label = intrinsic_genotype_label
              else:
                  fish_label = '; '.join((
                      str(intrinsic_genotype_label), extrinsic_label))
  
              fish_type = self.globaltt['effective_genotype']
  
              geno.addGenotype(fish_id, fish_label, fish_type)
              geno.addTaxon(taxon_id, fish_id)
  
              # since we re-create a label,
              # add the zfin fish label as the synonym
              model.addSynonym(fish_id, fish['fish_label'])
              self.id_label_map[fish_id] = fish_label
  
              if not self.test_mode and limit is not None and line_counter > limit:
                  break
  
              # ###finish iterating over fish
  
          # iterate of the alleles and attache the constructs to them
          LOG.info("Adding Allele/Construct relationships")
          for a in allele_to_construct_hash:
              if self.test_mode and a not in self.test_ids['allele']:
                  continue
              allele_id = 'ZFIN:' + a
              constructs = allele_to_construct_hash.get(a)
              if len(constructs) > 0:
                  for c in constructs:
                      cid = 'ZFIN:' + c
                      geno.addSequenceDerivesFrom(allele_id, cid)
                      # LOG.info("constructs for %s: %s", allele_id,
                      #             str(constructs))
                      # migrate the transgenic features to be alternate parts
                      # of the transgene insertion/alteration
                      if cid in self.transgenic_parts:
                          tg_parts = self.transgenic_parts.get(cid)
                          if tg_parts is not None:
                              for p in tg_parts:
                                  # HACK - if it's a promoter part,
                                  # then make it a simple has_part
                                  if re.search(r'promoter', p):
                                      r = self.globaltt['has_part']
                                  else:
                                      r = self.globaltt['has_variant_part']
                                  geno.addParts(p, allele_id, r)
  
          return