docs: def get_mutation_rates(transcripts, mut_dict, ensembl):
      """ determines mutation rates per functional category for transcripts
      
      Args:
          transcripts: list of transcript IDs for a gene
          mut_dict: dictionary of local sequence context mutation rates
          ensembl: EnsemblRequest object, to retrieve information from Ensembl.
      
      Returns:
          tuple of (rates, merged transcript, and transcript CDS length)
      """
      
      rates = {'missense': 0, 'nonsense': 0, 'splice_lof': 0,
          'splice_region': 0, 'synonymous': 0}
      combined = None
      
      for tx_id in transcripts:
          try:
              tx = construct_gene_object(ensembl, tx_id)
          except ValueError:
              continue
          
          if len(tx.get_cds_sequence()) % 3 != 0:
              raise ValueError("anomalous_coding_sequence")
          
          # ignore mitochondrial genes
          if tx.get_chrom() == "MT":
              continue
          
          sites = SiteRates(tx, mut_dict, masked_sites=combined)
          combined = tx + combined
          
          for cq in ['missense', 'nonsense', 'splice_lof', 'splice_region', 'synonymous']:
              rates[cq] += sites[cq].get_summed_rate()
      
      if combined is None:
          raise ValueError('no tx found')
      
      length = combined.get_coding_distance(combined.get_cds_end())['pos']
      
      return rates, combined, length