///|
fn max_relevance(relevance_map : Map[String, Int], threshold : Int) -> Int {
  let mut maximum = threshold
  for _, relevance in relevance_map {
    maximum = Int::max(maximum, relevance)
  }
  maximum
}

///|
pub fn graded_precision_at(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
  cutoff : Int,
  threshold : Int,
  gain_scheme : GainScheme,
) -> Double {
  ignore(threshold)
  let relevance_map = build_relevance_map(qrels)
  let ranked = sorted_run_items(run)
  let limit = Int::min(Int::max(cutoff, 0), ranked.length())
  let mut gain = 0.0
  let mut normalizer = 0.0
  for _, relevance in relevance_map {
    normalizer += gain_of(relevance, gain_scheme)
  }
  if normalizer == 0.0 {
    return 0.0
  }
  for index in 0.. Double {
  let relevance_map = build_relevance_map(qrels)
  let max_grade = max_relevance(relevance_map, threshold)
  let ranked = sorted_run_items(run)
  let limit = Int::min(Int::max(cutoff, 0), ranked.length())
  let mut continuation = 1.0
  let mut score = 0.0
  let denominator = @math.pow(2.0, Double::from_int(Int::max(max_grade, 1)))
  for index in 0.. Double {
  let relevance_map = build_relevance_map(qrels)
  let ranked = sorted_run_items(run)
  let limit = Int::min(Int::max(cutoff, 0), ranked.length())
  let p = Double::min(Double::max(persistence, 0.0), 1.0)
  let mut score = 0.0
  for index in 0..= threshold { 1.0 } else { 0.0 }
    score += (1.0 - p) * @math.pow(p, Double::from_int(index)) * hit
  }
  score
}