///|
pub fn parse_qrels_tsv(source : String) -> Result[Array[JudgedDoc], String] {
  let rows = match parse_tsv_fields(source, 3, "qrels") {
    Ok(rows) => rows
    Err(err) => return Err(err)
  }
  let parsed : Array[JudgedDoc] = []
  for index, fields in rows {
    let line_no = index + 1
    let relevance = match parse_int_field(fields[2], "relevance", line_no) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    parsed.push({ query_id: fields[0], doc_id: fields[1], relevance })
  }
  Ok(parsed)
}

///|
pub fn parse_run_tsv(source : String) -> Result[Array[RetrievedDoc], String] {
  let rows = match parse_tsv_fields(source, 3, "run") {
    Ok(rows) => rows
    Err(err) => return Err(err)
  }
  let parsed : Array[RetrievedDoc] = []
  for index, fields in rows {
    let line_no = index + 1
    let score = match parse_double_field(fields[2], "score", line_no) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    parsed.push({ query_id: fields[0], doc_id: fields[1], score })
  }
  Ok(parsed)
}

///|
pub fn parse_candidate_pool_tsv(
  source : String,
) -> Result[Array[CandidatePool], String] {
  let rows = match parse_tsv_fields(source, 2, "candidate pool") {
    Ok(rows) => rows
    Err(err) => return Err(err)
  }
  let grouped : Map[String, Array[String]] = Map([])
  for fields in rows {
    let bucket = grouped.get_or_init(fields[0], fn() { [] })
    bucket.push(fields[1])
  }
  let pools : Array[CandidatePool] = []
  for query_id, doc_ids in grouped {
    pools.push({ query_id, doc_ids })
  }
  pools.sort_by(fn(a, b) { a.query_id.compare(b.query_id) })
  Ok(pools)
}

///|
pub fn build_candidate_pools(run : Array[RetrievedDoc]) -> Array[CandidatePool] {
  let grouped : Map[String, Array[String]] = Map([])
  for item in run {
    let bucket = grouped.get_or_init(item.query_id, fn() { [] })
    if !bucket.contains(item.doc_id) {
      bucket.push(item.doc_id)
    }
  }
  let pools : Array[CandidatePool] = []
  for query_id, doc_ids in grouped {
    pools.push({ query_id, doc_ids })
  }
  pools.sort_by(fn(a, b) { a.query_id.compare(b.query_id) })
  pools
}