blob: 86c2bbd0272f730cc7eb6258a352918b116a14f6 [file] [log] [blame]
package io.prediction.output.itemrank
import io.prediction.commons.Config
import io.prediction.commons.appdata.{ Item, Items }
import io.prediction.commons.modeldata.ItemRecScore
import io.prediction.commons.settings.{ Algo, App, Engine, OfflineEval }
import com.github.nscala_time.time.Imports._
trait ItemRankAlgoOutput {
/** output the Seq of iids */
def output(uid: String, iids: Seq[String], instant: DateTime)(implicit app: App, algo: Algo,
offlineEval: Option[OfflineEval]): Seq[(String, Double)]
}
object ItemRankAlgoOutput {
val config = new Config
def freshnessOutput(output: Seq[(Item, Double)])(implicit app: App,
engine: Engine, items: Items) = {
val freshness = engine.params.get("freshness").map(_.asInstanceOf[Int])
val freshnessTimeUnit = engine.params.get("freshnessTimeUnit").map(
_.asInstanceOf[Long])
/** Freshness output. */
(freshness, freshnessTimeUnit) match {
case (Some(f), Some(ftu)) => if (f > 0) {
val recommendationTime = DateTime.now.millis
output.map { itemAndScore =>
val item = itemAndScore._1
item.starttime map { st =>
val timeDiff = (recommendationTime - st.millis) / 1000 / ftu
if (timeDiff > 0)
(itemAndScore._1, itemAndScore._2 * scala.math.exp(-timeDiff /
(11 - f)))
else
itemAndScore
} getOrElse itemAndScore
}.sortBy(t => t._2).reverse
} else output
case _ => output
}
}
/**
* The ItemRec output does the following in sequence:
*
* - determine capabilities that needs to be handled by the engine
* - compute the number of items for the engine to postprocess
* - perform mandatory filtering (geo, time)
* - perform postprocessing capabilities
* - output items
*/
def output(uid: String, iids: Seq[String])(implicit app: App, engine: Engine,
algo: Algo, offlineEval: Option[OfflineEval] = None): Seq[String] = {
val algoInfos = config.getSettingsAlgoInfos
implicit val items = offlineEval map { _ =>
config.getAppdataTrainingItems
} getOrElse { config.getAppdataItems }
/**
* Determine capability of algo to see what this engine output layer needs
* to handle.
*/
val engineCapabilities = Seq("freshness")
val algoCapabilities = algoInfos.get(algo.infoid).map(_.capabilities).
getOrElse(Seq())
val handledByEngine = engineCapabilities.filterNot(
algoCapabilities.contains(_))
// Time-dependent logic should reference to the same instant.
val instant = DateTime.now
/**
* If no recommendations found for this user, simply return the original
* list.
*/
val ranked = ItemRankAllItemOutput.output(uid, iids, instant)
val (rankedIids, rankedScores) = ranked.unzip
val rankedIidsSet = rankedIids.toSet
val rankedItemsMap = items.getByIds(app.id, rankedIids).map(x => x.id -> x)
.toMap
val rankedItems = rankedIids.map(x => rankedItemsMap(x))
val unranked = iids.filterNot(x => rankedIidsSet(x))
val preFinalOutput = handledByEngine
.foldLeft(rankedItems.zip(rankedScores)) {
(output, cap) =>
cap match {
case "freshness" => freshnessOutput(output)
}
}
preFinalOutput.map(_._1.id) ++ unranked
}
}