      :empty? (not (seq matches))
      :matches (zipmap matches (repeat 1))}))
 
+(s/fdef union-matches
+        :args (s/cat :metrics (s/coll-of ::metrics))
+        :ret ::matches)
 (defn- union-matches
   "Set union matches."
   [metrics]
   (transduce (map :matches) (partial merge-with +) metrics))
 
+(s/fdef aggregate-fold-metrics
+        :args (s/cat :metrics (s/coll-of ::metrics))
+        :ret (s/keys :req-un [::matches ::empties ::ranks]))
 (defn aggregate-fold-metrics
   "Compute aggregate metrics for an evaluation fold."
   [metrics]

   :empties (sequence (map :empty?) metrics) ; FIXME: Needless double iteration.
   :ranks (sequence (map :rank) metrics)})

(defn aggregate-evaluation-metrics
  "Compute aggregate metrics for the whole evaluation."
  [{:keys [bidder-count contract-count long-tail?]}
   metrics]
  (let [matches (union-matches metrics)
        long-tail-matches-count (->> matches
                                     (filter (comp long-tail? key))
                                     (transduce (map val) +))]
    {:catalog-coverage (/ (count matches) bidder-count)
     :long-tail-percentage (/ long-tail-matches-count contract-count)
     :prediction-coverage (/ (count (filter false? (mapcat :empties metrics))) contract-count)
     :ranks (mapcat :ranks metrics)}))

(ns matchmaker-sparql.evaluation.metrics
  (:require [clojure.set :refer [union]]
            [clojure.spec :as s]
            [sparclj.core :as sparql]
            [taoensso.timbre :as timbre]))

; ----- Specs -----

; ----- Generic specs -----

(s/def ::fraction (s/and number? #(<= 0 % 1)))

(s/def ::positive-integer (s/and integer? pos?))

; ----- Concrete specs -----

(s/def ::empty? boolean?)

(s/def ::empties (s/coll-of ::empty?))

(s/def ::matches (s/map-of ::sparql/iri ::positive-integer))

(s/def ::rank (s/or :position ::positive-integer
                    :not-found #{:not-found}))

(s/def ::ranks (s/coll-of ::rank))

(s/def ::metrics (s/keys :req-un [::rank ::empty? ::matches]))

(s/fdef compute-metrics
        :args (s/cat :match ::sparql/iri
                     :matches (s/coll-of ::sparql/iri :distinct true))
        :ret ::metrics)
(defn compute-metrics
  "Compute metrics of `matches` given correct `match`."
  [match matches]
