Repository: haddocking/haddock3
Issue: correct ordering of capri_clt

lines in capri cluster file are not ordered correctly. The `cluster_rank` field is correct, but the file is not sorted accordingly.

Changed files:
src/haddock/modules/analysis/caprieval/capri.py, tests/test_module_caprieval.py

diff --git a/src/haddock/modules/analysis/caprieval/capri.py b/src/haddock/modules/analysis/caprieval/capri.py
index 50de0103..e2baeb8c 100644
--- a/src/haddock/modules/analysis/caprieval/capri.py
+++ b/src/haddock/modules/analysis/caprieval/capri.py
@@ -733,9 +733,10 @@ def capri_cluster_analysis(
         path
         ):
     """Consider the cluster results for the CAPRI evaluation."""
+    log.info(f"Rearranging cluster information into {output_fname}")
     # get the cluster data
     clt_data = dict(((m.clt_rank, m.clt_id), []) for m in model_list)
-
+    
     # add models to each cluster
     for capri, model in zip(capri_list, model_list):
         clt_data[(model.clt_rank, model.clt_id)].append((capri, model))
@@ -802,10 +803,32 @@ def capri_cluster_analysis(
         data["fnat_std"] = fnat_stdev
         data["lrmsd"] = lrmsd_mean
         data["lrmsd_std"] = lrmsd_stdev
-        data["dockqn"] = dockq_mean
+        data["dockq"] = dockq_mean
         data["dockq_std"] = dockq_stdev
 
         output_dic[i] = data
+    
+    # Rank according to the score
+    score_rankkey_values = [(key, output_dic[key]['score'])
+                            for key in output_dic.keys()]
+    score_rankkey_values.sort(key=lambda x: x[1])
+    for i, k in enumerate(score_rankkey_values):
+        idx, _ = k
+        output_dic[idx]["caprieval_rank"] = i + 1
+
+    # Rank according to the sorting key
+    rankkey_values = [(key, output_dic[key][sort_key])
+                      for key in output_dic.keys()]
+    rankkey_values.sort(
+        key=lambda x: x[1],
+        reverse=True if not sort_ascending else False
+        )
+
+    _output_dic = {}
+    for i, k in enumerate(rankkey_values):
+        idx, _ = k
+        _output_dic[i + 1] = output_dic[idx]
+    output_dic = _output_dic
 
     output_fname = Path(path, output_fname)
 
diff --git a/tests/test_module_caprieval.py b/tests/test_module_caprieval.py
index 3c3fd666..cda7dc2b 100644
--- a/tests/test_module_caprieval.py
+++ b/tests/test_module_caprieval.py
@@ -33,6 +33,12 @@ def remove_aln_files(class_name):
             os.unlink(f)
 
 
+def read_capri_file(fname):
+    file_content = [e.split()[1:] for e in open(fname).readlines()
+                    if not e.startswith('#')]
+    return file_content
+
+
 @pytest.fixture
 def protprot_input_list():
     """Prot-prot input."""
@@ -181,7 +187,7 @@ def test_protprot_dockq(protprot_caprimodule):
     protprot_caprimodule.lrmsd = 15.9
     protprot_caprimodule.calc_dockq()
     assert round_two_dec(protprot_caprimodule.dockq) == 0.10
-    
+
 
 def test_protlig_irmsd(protlig_caprimodule):
     """Test protein-ligand i-rmsd calculation."""
@@ -248,8 +254,7 @@ def test_make_output(protprot_caprimodule):
 
     # remove the model column since its name will depend on where we are running
     #  the test
-    observed_outf_l = [e.split()[1:] for e in open(
-        ss_fname).readlines() if not e.startswith('#')]
+    observed_outf_l = read_capri_file(ss_fname)
     expected_outf_l = [
         ['md5', 'caprieval_rank', 'score', 'irmsd', 'fnat', 'lrmsd', 'ilrmsd',
          'dockq', 'cluster-id', 'cluster-ranking', 'model-cluster-ranking'],
@@ -399,17 +404,17 @@ def test_calc_stats():
 
 def test_capri_cluster_analysis(protprot_caprimodule, protprot_input_list):
     """Test the cluster analysis."""
-    model = protprot_input_list[0]
-    model.clt_rank = 1
-    model.clt_id = 1
-    model.score = 42.0
+    model1, model2 = protprot_input_list[0], protprot_input_list[1]
+    model1.clt_rank, model2.clt_rank = 1, 2
+    model1.clt_id, model2.clt_id = 1, 2
+    model1.score, model2.score = 42.0, 50.0
     protprot_caprimodule.irmsd = 0.1
     protprot_caprimodule.fnat = 1.0
     protprot_caprimodule.lrmsd = 1.2
     protprot_caprimodule.ilrmsd = 4.3
     capri_cluster_analysis(
-        capri_list=[protprot_caprimodule],
-        model_list=[model],
+        capri_list=[protprot_caprimodule, protprot_caprimodule],
+        model_list=[model1, model2],
         output_fname="capri_clt.txt",
         clt_threshold=5,
         sort_key="score",
@@ -418,6 +423,37 @@ def test_capri_cluster_analysis(protprot_caprimodule, protprot_input_list):
 
     assert Path('capri_clt.txt').stat().st_size != 0
 
+    observed_outf_l = read_capri_file("capri_clt.txt")
+    expected_outf_l = [
+        ['cluster_id', 'n', 'under_eval', 'score', 'score_std', 'irmsd',
+         'irmsd_std', 'fnat', 'fnat_std', 'lrmsd', 'lrmsd_std', 'dockq',
+         'dockq_std', 'caprieval_rank'],
+        ['1', '1', 'yes', '42.000', '0.000', '0.100', '0.000', '1.000',
+         '0.000', '1.200', '0.000', 'nan', 'nan', '1'],
+        ['2', '1', 'yes', '50.000', '0.000', '0.100', '0.000', '1.000',
+         '0.000', '1.200', '0.000', 'nan', 'nan', '2']]
+    assert observed_outf_l == expected_outf_l
+    
+    # test sorting
+    capri_cluster_analysis(
+        capri_list=[protprot_caprimodule, protprot_caprimodule],
+        model_list=[model1, model2],
+        output_fname="capri_clt.txt",
+        clt_threshold=5,
+        sort_key="cluster_rank",
+        sort_ascending=False,
+        path=Path("."))
+    
+    observed_outf_l = read_capri_file("capri_clt.txt")
+    expected_outf_l = [
+        ['cluster_id', 'n', 'under_eval', 'score', 'score_std', 'irmsd',
+         'irmsd_std', 'fnat', 'fnat_std', 'lrmsd', 'lrmsd_std', 'dockq',
+         'dockq_std', 'caprieval_rank'],
+        ['2', '1', 'yes', '50.000', '0.000', '0.100', '0.000', '1.000',
+         '0.000', '1.200', '0.000', 'nan', 'nan', '2'],
+        ['1', '1', 'yes', '42.000', '0.000', '0.100', '0.000', '1.000',
+         '0.000', '1.200', '0.000', 'nan', 'nan', '1']]
+
     Path('capri_clt.txt').unlink()