Repository: deepset-ai/haystack

Issue:
JoinDocuments should use highest score when multiple retrievers recall the same document
**Is your feature request related to a problem? Please describe.**
The JoinDocuments node currently uses the document from the last retriever if their are multiple retrievers and they recall the same document. The first retriever could have the highest score and be more useful. That's why I propose to use the document with the highest score if there are duplicate documents recalled



Discussion:
Hi @Koenlaermans I completely agree! It's a well defined, small issue so I will also add the Contributions Wanted Label here in case you ore somebody else would like to contribute to Haystack by fixing this issue. The starting point for a fix is here in the code: https://github.com/deepset-ai/haystack/blob/56d033e7e77841b517cf2a75c03714c3f6cc7830/haystack/nodes/other/join_docs.py#L62 
Thank you for reporting this!

Resolving patch:
diff --git a/haystack/nodes/other/join_docs.py b/haystack/nodes/other/join_docs.py
--- a/haystack/nodes/other/join_docs.py
+++ b/haystack/nodes/other/join_docs.py
@@ -1,11 +1,10 @@
-from collections import defaultdict
 import logging
+from collections import defaultdict
 from math import inf
+from typing import List, Optional
 
-from typing import Optional, List
-
-from haystack.schema import Document
 from haystack.nodes.other.join import JoinNode
+from haystack.schema import Document
 
 logger = logging.getLogger(__name__)
 
@@ -64,7 +63,7 @@ def run_accumulated(self, inputs: List[dict], top_k_join: Optional[int] = None):
         document_map = {doc.id: doc for result in results for doc in result}
 
         if self.join_mode == "concatenate":
-            scores_map = self._concatenate_results(results)
+            scores_map = self._concatenate_results(results, document_map)
         elif self.join_mode == "merge":
             scores_map = self._calculate_comb_sum(results)
         elif self.join_mode == "reciprocal_rank_fusion":
@@ -118,11 +117,22 @@ def run_batch_accumulated(self, inputs: List[dict], top_k_join: Optional[int] =
 
             return output, "output_1"
 
-    def _concatenate_results(self, results):
+    def _concatenate_results(self, results, document_map):
         """
         Concatenates multiple document result lists.
+        Return the documents with the higher score.
         """
-        return {doc.id: doc.score for result in results for doc in result}
+        list_id = list(document_map.keys())
+        scores_map = {}
+        for idx in list_id:
+            tmp = []
+            for result in results:
+                for doc in result:
+                    if doc.id == idx:
+                        tmp.append(doc)
+            item_best_score = max(tmp, key=lambda x: x.score)
+            scores_map.update({idx: item_best_score.score})
+        return scores_map
 
     def _calculate_comb_sum(self, results):
         """


Test patch:
diff --git a/test/nodes/test_join_documents.py b/test/nodes/test_join_documents.py
--- a/test/nodes/test_join_documents.py
+++ b/test/nodes/test_join_documents.py
@@ -54,3 +54,27 @@ def test_joindocuments_preserves_root_node():
     join_docs = JoinDocuments()
     result, _ = join_docs.run(inputs)
     assert result["root_node"] == "File"
+
+
+@pytest.mark.unit
+def test_joindocuments_concatenate_keep_only_highest_ranking_duplicate():
+    inputs = [
+        {
+            "documents": [
+                Document(content="text document 1", content_type="text", score=0.2),
+                Document(content="text document 2", content_type="text", score=0.3),
+            ]
+        },
+        {"documents": [Document(content="text document 2", content_type="text", score=0.7)]},
+    ]
+    expected_outputs = {
+        "documents": [
+            Document(content="text document 2", content_type="text", score=0.7),
+            Document(content="text document 1", content_type="text", score=0.2),
+        ]
+    }
+
+    join_docs = JoinDocuments(join_mode="concatenate")
+    result, _ = join_docs.run(inputs)
+    assert len(result["documents"]) == 2
+    assert result["documents"] == expected_outputs["documents"]