docs: def parse_coach_bsites_inf(infile):
      """Parse the Bsites.inf output file of COACH and return a list of rank-ordered binding site predictions
  
      Bsites.inf contains the summary of COACH clustering results after all other prediction algorithms have finished
      For each site (cluster), there are three lines:
      
          - Line 1: site number, c-score of coach prediction, cluster size
          - Line 2: algorithm, PDB ID, ligand ID, center of binding site (cartesian coordinates), 
            c-score of the algorithm's prediction, binding residues from single template
          - Line 3: Statistics of ligands in the cluster
  
      C-score information:
  
          - "In our training data, a prediction with C-score>0.35 has average false positive and false negative rates below
            0.16 and 0.13, respectively." (https://zhanglab.ccmb.med.umich.edu/COACH/COACH.pdf)
  
      Args:
          infile (str): Path to Bsites.inf
  
      Returns:
          list: Ranked list of dictionaries, keys defined below
          
              - ``site_num``: cluster which is the consensus binding site
              - ``c_score``: confidence score of the cluster prediction
              - ``cluster_size``: number of predictions within this cluster
              - ``algorithm``: main? algorithm used to make the prediction
              - ``pdb_template_id``: PDB ID of the template used to make the prediction
              - ``pdb_template_chain``: chain of the PDB which has the ligand
              - ``pdb_ligand``: predicted ligand to bind
              - ``binding_location_coords``: centroid of the predicted ligand position in the homology model
              - ``c_score_method``: confidence score for the main algorithm
              - ``binding_residues``: predicted residues to bind the ligand
              - ``ligand_cluster_counts``: number of predictions per ligand
  
      """
  
      bsites_results = []
  
      with open(infile) as pp:
          lines = list(filter(None, (line.rstrip() for line in pp)))
  
      for i in range(len(lines) // 3):
          bsites_site_dict = {}
  
          line1 = lines[i * 3].split('\t')
          line2 = lines[i * 3 + 1].split('\t')
          line3 = lines[i * 3 + 2]
  
          bsites_site_dict['site_num'] = line1[0]
          bsites_site_dict['c_score'] = float(line1[1])
          bsites_site_dict['cluster_size'] = line1[2]
  
          bsites_site_dict['algorithm'] = line2[0]
          bsites_site_dict['pdb_template_id'] = line2[1][:4]
          bsites_site_dict['pdb_template_chain'] = line2[1][4]
          bsites_site_dict['pdb_ligand'] = line2[2]
          bsites_site_dict['binding_location_coords'] = tuple(float(x) for x in line2[3].split())
  
          # TODO: what's the difference between this c-score and the cluster's c-score?
          # how is the cluster's c-score computed? it's not the average c-score of all methods
          # also why are some COFACTOR c-scores >1?
          # 160411 - seems like the COFACTOR "BS-score" is being reported here, not its c-score...
          tmp_split = line2[4].split(' :')
          bsites_site_dict['c_score_method'] = tmp_split[0]
          bsites_site_dict['binding_residues'] = tmp_split[1]
  
          bsites_site_dict['ligand_cluster_counts'] = line3
  
          bsites_results.append(bsites_site_dict)
  
      return bsites_results