///|
fn ranked_query_run(
  run : Array[RetrievedDoc],
  query_id : String,
) -> Array[RetrievedDoc] {
  group_runs(run).get_or_default(query_id, [])
}

///|
pub fn retrieved_document_count(run : Array[RetrievedDoc]) -> Int {
  let unique : Map[String, Unit] = Map([])
  for item in run {
    unique["\{item.query_id}:\{item.doc_id}"] = ()
  }
  unique.length()
}

///|
pub fn relevant_rank_positions(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  threshold : Int,
) -> Array[Int] {
  if qrels.is_empty() {
    return []
  }
  let relevant = build_relevance_map(qrels)
  let positions : Array[Int] = []
  let ranked = ranked_query_run(run, qrels[0].query_id)
  for index in 0..= threshold {
      positions.push(index + 1)
    }
  }
  positions
}

///|
pub fn first_relevant_rank(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  threshold : Int,
) -> Int {
  let positions = if qrels.is_empty() {
    []
  } else {
    relevant_rank_positions(qrels, run, threshold)
  }
  if positions.is_empty() {
    0
  } else {
    positions[0]
  }
}

///|
pub fn recall_curve(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  cutoff : Int,
  threshold : Int,
) -> Array[Double] {
  let curve : Array[Double] = []
  for rank in 1..<=Int::max(cutoff, 0) {
    curve.push(recall_at(qrels, run, rank, threshold))
  }
  curve
}

///|
pub fn precision_curve(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  cutoff : Int,
  threshold : Int,
) -> Array[Double] {
  let curve : Array[Double] = []
  for rank in 1..<=Int::max(cutoff, 0) {
    curve.push(precision_at(qrels, run, rank, threshold))
  }
  curve
}

///|
pub fn precision_recall_points(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  cutoff : Int,
  threshold : Int,
) -> Array[RankPoint] {
  let precision = precision_curve(qrels, run, cutoff, threshold)
  let recall = recall_curve(qrels, run, cutoff, threshold)
  let points : Array[RankPoint] = []
  for index in 0.. Double {
  let precision = precision_curve(qrels, run, cutoff, threshold)
  let recall = recall_curve(qrels, run, cutoff, threshold)
  if precision.length() <= 1 {
    if precision.is_empty() {
      0.0
    } else {
      precision[0] * recall[0]
    }
  } else {
    let mut area = 0.0
    for index in 1.. Array[String] {
  let ranked = ranked_query_run(run, query_id)
  let result : Array[String] = []
  let seen : Map[String, Unit] = Map([])
  let limit = Int::min(Int::max(cutoff, 0), ranked.length())
  for index in 0.. Double {
  let left_docs = unique_documents_at(left, query_id, cutoff)
  let right_docs = unique_documents_at(right, query_id, cutoff)
  let left_set : Map[String, Unit] = Map([])
  let right_set : Map[String, Unit] = Map([])
  for doc_id in left_docs {
    left_set[doc_id] = ()
  }
  for doc_id in right_docs {
    right_set[doc_id] = ()
  }
  let mut intersection = 0
  for doc_id, _ in left_set {
    if right_set.contains(doc_id) {
      intersection += 1
    }
  }
  let union = left_set.length() + right_set.length() - intersection
  to_ratio(intersection, union)
}

///|
pub fn rank_biased_overlap(
  left : Array[RetrievedDoc],
  right : Array[RetrievedDoc],
  query_id : String,
  cutoff : Int,
  persistence? : Double = 0.8,
) -> Double {
  let p = Double::min(Double::max(persistence, 0.0), 1.0)
  let left_docs = unique_documents_at(left, query_id, cutoff)
  let right_docs = unique_documents_at(right, query_id, cutoff)
  let left_seen : Map[String, Unit] = Map([])
  let right_seen : Map[String, Unit] = Map([])
  let mut total = 0.0
  for rank in 0..