
    $j|              	           d Z ddlmZ ddlmZmZ ddeee                  dedee         fd	Zd
ee	e
ef                  dee
         dee	e
ef                  fdZdS )z?
Helper functions for hybrid search (semantic + BM25 + graph).
    )Any   )MergedCandidateRetrievalResult<   result_listskreturnc                 4   i }i }i }g d}t          |           D ]	\  }}|t          |          k     r||         nd| }t          |d          D ]\  }	}
t          |
t                    r6t	          d| d|	 dt          |
          dk    r
|
d	d         n|
 d
          t          |
t
                    s*t	          dt          |
          j         d| d|	           |
j        }||vr|
||<   ||vr
d||<   i ||<   ||xx         d||	z   z  z  cc<   |	||         | d<   Ґg }t          t          |
                                d d          d          D ]<\  }\  }}t          ||         ||||                   }|                    |           =|S )a  
    Merge multiple ranked result lists using Reciprocal Rank Fusion.

    RRF formula: score(d) = sum_over_lists(1 / (k + rank(d)))

    Args:
        result_lists: List of result lists, each containing RetrievalResult objects
        k: Constant for RRF formula (default: 60)

    Returns:
        Merged list of MergedCandidate objects, sorted by RRF score

    Example:
        semantic_results = [RetrievalResult(...), RetrievalResult(...), ...]
        bm25_results = [RetrievalResult(...), RetrievalResult(...), ...]
        graph_results = [RetrievalResult(...), RetrievalResult(...), ...]

        merged = reciprocal_rank_fusion([semantic_results, bm25_results, graph_results])
        # Returns: [MergedCandidate(...), MergedCandidate(...), ...]
    )semanticbm25graphtemporalsource_r   )startz*Expected RetrievalResult but got tuple in z results at rank z. Tuple value:    NzZ. This suggests the retrieval function returned tuples instead of RetrievalResult objects.z!Expected RetrievalResult but got z in         g      ?_rankc                     | d         S )Nr    )xs    j/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/engine/search/fusion.py<lambda>z(reciprocal_rank_fusion.<locals>.<lambda>F   s
    1     T)keyreverse)	retrieval	rrf_scorerrf_ranksource_ranks)	enumeratelen
isinstancetuple	TypeErrorr   type__name__idsorteditemsr   append)r   r	   
rrf_scoresr    all_retrievalssource_names
source_idxresultssource_namerankr   doc_idmerged_resultsr   r   merged_candidates                   r   reciprocal_rank_fusionr6   
   sl   , JLN<<<L(66 ? ?
G2<s<?P?P2P2Pl:..Vl`jVlVl(::: 	? 	?OD))U++ p p p_c p p58^^q5H5HIbqbMMip p p  
 i99 zY8PzzVazztxzz   \F ^++)2v& Z''%(
6"')V$v#T"22:>L K!6!6!6771	?6 N)2z!!~~tDDDA* * * 0 0%%69 +$V,	Hcopvcw
 
 
 	.////r   r0   
score_keysc                     |D ]kfd| D             }|st          |          }t          |          }||z
  }|dk    r| D ]}|v r|         |z
  |z  | d<   Z| D ]}|v rd| d<   l| S )aw  
    Normalize scores based on deltas (min-max normalization within result set).

    This ensures all scores are in [0, 1] range based on the spread in THIS result set.

    Args:
        results: List of result dicts
        score_keys: Keys to normalize (e.g., ["recency", "frequency"])

    Returns:
        Results with normalized scores added as "{key}_normalized"
    c                 D    g | ]}|v |                     d           S )r   )get).0rr   s     r   
<listcomp>z.normalize_scores_on_deltas.<locals>.<listcomp>^   s*    ???acQhh!%%S//hhhr   r   _normalizedg      ?)minmax)r0   r7   valuesmin_valmax_valdeltar<   r   s          @r   normalize_scores_on_deltasrE   P   s      1 1????7??? 	f++f++'!199 H H!88./fw.>%-GA)))*H
  1 1!88-0A)))*1 Nr   N)r   )__doc__typingr   typesr   r   listintr6   dictstrrE   r   r   r   <module>rM      s           3 3 3 3 3 3 3 3C Cd?.C)D C CVZ[jVk C C C CL!T#s(^(< !$s) !X\]abegjbj]kXl ! ! ! ! ! !r   