def ndcg_at_k(
        self,
        query: str,
        retrieved_context_chunks: List[str],
        relevance_scores: Optional[List[float]] = None,
        k: Optional[int] = None,
    ) -> float:
        """
        Compute NDCG@k for a given query and retrieved context chunks.

        Args:
            query (str): The input query string.
            retrieved_context_chunks (List[str]): List of retrieved context chunks.
            relevance_scores (Optional[List[float]]): Relevance scores for each retrieved chunk.
            k (Optional[int]): Rank position up to which to compute NDCG. If None, compute for all retrieved chunks.

        Returns:
            float: Computed NDCG@k score.
        """
        # Step 1: Find the ground truth context chunks for the given query
        ground_truth_context_chunks_and_scores = (
            self._find_golden_context_chunks_and_scores(query)
        )
        if ground_truth_context_chunks_and_scores:
            k = k or len(
                retrieved_context_chunks
            )  # If k is None, use all retrieved chunks

            # Step 2: Extract ground truth chunks and their relevance scores
            golden_chunks = [
                chunk[0] for chunk in ground_truth_context_chunks_and_scores
            ]
            golden_scores = [
                chunk[1] for chunk in ground_truth_context_chunks_and_scores
            ]

            # Step 3: If relevance scores are provided, sort retrieved chunks by scores in descending order
            if relevance_scores:
                # Zip together retrieved chunks and relevance scores
                retrieved_with_scores = list(
                    zip(retrieved_context_chunks, relevance_scores)
                )
                # Sort by scores in descending order
                retrieved_with_scores.sort(key=lambda x: x[1], reverse=True)
                # Extract the sorted chunks
                retrieved_context_chunks = [
                    chunk for chunk, _ in retrieved_with_scores
                ]

            # Step 4: Create a binary relevance vector for the retrieved chunks based on golden chunks
            rel_scores = [0.0] * len(
                retrieved_context_chunks
            )  # Initialize with 0 relevance for all
            for i, chunk in enumerate(retrieved_context_chunks[:k]):
                if chunk in golden_chunks:
                    index_in_golden = golden_chunks.index(chunk)
                    rel_scores[i] = golden_scores[
                        index_in_golden
                    ]  # Use the true relevance score

            # Step 5: Prepare the ground truth scores as a vector
            # Ideal DCG (IDCG) is calculated by placing all relevant items at the top in descending order
            ideal_golden_scores = sorted(golden_scores, reverse=True)[:k]

            # Step 6: Calculate NDCG@k using sklearn's ndcg_score
            return ndcg_score(
                y_true=np.array([ideal_golden_scores]),
                y_score=np.array([rel_scores[:k]]),  # Consider only top-k items
                k=k,
            )
        else:
            return np.nan

        Compute NDCG@k for a given query and retrieved context chunks.

        Args:
            query (str): The input query string.
            retrieved_context_chunks (List[str]): List of retrieved context chunks.
            relevance_scores (Optional[List[float]]): Relevance scores for each retrieved chunk.
            k (Optional[int]): Rank position up to which to compute NDCG. If None, compute for all retrieved chunks.

        Returns:
            float: Computed NDCG@k score.
        
src/feedback/trulens/feedback/groundtruth.py