3030import java .util .Arrays ;
3131import java .util .Comparator ;
3232import java .util .Set ;
33+ import java .util .Collections ;
34+ import static java .util .stream .Collectors .toSet ;
3335
3436public final class QueryRescorer implements Rescorer {
3537
@@ -61,6 +63,11 @@ protected float combine(float firstPassScore, boolean secondPassMatches, float s
6163 // First take top slice of incoming docs, to be rescored:
6264 TopDocs topNFirstPass = topN (topDocs , rescoreContext .getWindowSize ());
6365
66+ // Save doc IDs for which rescoring was applied to be used in score explanation
67+ Set <Integer > topNDocIDs = Collections .unmodifiableSet (
68+ Arrays .stream (topNFirstPass .scoreDocs ).map (scoreDoc -> scoreDoc .doc ).collect (toSet ()));
69+ rescoreContext .setRescoredDocs (topNDocIDs );
70+
6471 // Rescore them:
6572 TopDocs rescored = rescorer .rescore (searcher , topNFirstPass , rescoreContext .getWindowSize ());
6673
@@ -71,16 +78,12 @@ protected float combine(float firstPassScore, boolean secondPassMatches, float s
7178 @ Override
7279 public Explanation explain (int topLevelDocId , IndexSearcher searcher , RescoreContext rescoreContext ,
7380 Explanation sourceExplanation ) throws IOException {
74- QueryRescoreContext rescore = (QueryRescoreContext ) rescoreContext ;
7581 if (sourceExplanation == null ) {
7682 // this should not happen but just in case
7783 return Explanation .noMatch ("nothing matched" );
7884 }
79- // TODO: this isn't right? I.e., we are incorrectly pretending all first pass hits were rescored? If the requested docID was
80- // beyond the top rescoreContext.window() in the first pass hits, we don't rescore it now?
81- Explanation rescoreExplain = searcher .explain (rescore .query (), topLevelDocId );
85+ QueryRescoreContext rescore = (QueryRescoreContext ) rescoreContext ;
8286 float primaryWeight = rescore .queryWeight ();
83-
8487 Explanation prim ;
8588 if (sourceExplanation .isMatch ()) {
8689 prim = Explanation .match (
@@ -89,23 +92,24 @@ public Explanation explain(int topLevelDocId, IndexSearcher searcher, RescoreCon
8992 } else {
9093 prim = Explanation .noMatch ("First pass did not match" , sourceExplanation );
9194 }
92-
93- // NOTE: we don't use Lucene's Rescorer.explain because we want to insert our own description with which ScoreMode was used. Maybe
94- // we should add QueryRescorer.explainCombine to Lucene?
95- if (rescoreExplain != null && rescoreExplain .isMatch ()) {
96- float secondaryWeight = rescore .rescoreQueryWeight ();
97- Explanation sec = Explanation .match (
95+ if (rescoreContext .isRescored (topLevelDocId )){
96+ Explanation rescoreExplain = searcher .explain (rescore .query (), topLevelDocId );
97+ // NOTE: we don't use Lucene's Rescorer.explain because we want to insert our own description with which ScoreMode was used.
98+ // Maybe we should add QueryRescorer.explainCombine to Lucene?
99+ if (rescoreExplain != null && rescoreExplain .isMatch ()) {
100+ float secondaryWeight = rescore .rescoreQueryWeight ();
101+ Explanation sec = Explanation .match (
98102 rescoreExplain .getValue () * secondaryWeight ,
99103 "product of:" ,
100104 rescoreExplain , Explanation .match (secondaryWeight , "secondaryWeight" ));
101- QueryRescoreMode scoreMode = rescore .scoreMode ();
102- return Explanation .match (
105+ QueryRescoreMode scoreMode = rescore .scoreMode ();
106+ return Explanation .match (
103107 scoreMode .combine (prim .getValue (), sec .getValue ()),
104108 scoreMode + " of:" ,
105109 prim , sec );
106- } else {
107- return prim ;
110+ }
108111 }
112+ return prim ;
109113 }
110114
111115 private static final Comparator <ScoreDoc > SCORE_DOC_COMPARATOR = new Comparator <ScoreDoc >() {
0 commit comments