Skip to content

Commit

Permalink
feat: add helpers to run banner auctions
Browse files Browse the repository at this point in the history
  • Loading branch information
fcs-ts committed Aug 8, 2024
1 parent 59452f8 commit f80f07d
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.topsort.analytics.banners

/**
* Class that handles different type of Banner configurations
*/
sealed class BannerConfig private constructor() {

/**
* Banner configuration for landing page banners
*
* @property slotId id of the banner slot
* @property ids ids of the entities that are competing for the banner
* @property device can be "desktop" or "mobile"
* @property geoTargeting optional location for geo-targeted banners
*/
data class LandingPage(
val slotId: String,
val ids: List<String>,
val device: String? = null,
val geoTargeting: String? = null
) : BannerConfig()

/**
* Banner configuration for single category banners
*
* @property slotId id of the banner slot
* @property category category for the banner
* @property device can be "desktop" or "mobile"
* @property geoTargeting optional location for geo-targeted banners
*/
data class CategorySingle(
val slotId: String,
val category: String,
val device: String? = null,
val geoTargeting: String? = null
) : BannerConfig()

/**
* Banner config for multiple category banners
*
* @property slotId id of the banner slot
* @property categories list of categories for the competing banners
* @property device can be "desktop" or "mobile"
* @property geoTargeting optional location for geo-targeted banners
*/
data class CategoryMultiple(
val slotId: String,
val categories: List<String>,
val device: String? = null,
val geoTargeting: String? = null,
) : BannerConfig()

/**
* Banner configuration for category disjunctions banners
*
* @property slotId id of the banner slot
* @property disjunctions category disjunctions for the competing banners
* @property device can be "desktop" or "mobile"
* @property geoTargeting optional location for geo-targeted banners
*/
data class CategoryDisjunctions(
val slotId: String,
val disjunctions: List<List<String>>,
val device: String? = null,
val geoTargeting: String? = null,
) : BannerConfig()

/**
* Banner configuration for keyword banners
*
* @property slotId id of the banner slot
* @property keyword keyword for the competing banners
* @property device can be "desktop" or "mobile"
* @property geoTargeting optional location for geo-targeted banners
*/
data class Keyword(
val slotId: String,
val keyword: String,
val device: String? = null,
val geoTargeting: String? = null,
) : BannerConfig()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.topsort.analytics.banners

import com.topsort.analytics.model.auctions.EntityType

/**
* Response for a single slot banner auction
*
* @property id id of the winning entity
* @property type type of the winning entity
* @property url url of the banner to show
* @property resolvedBidId id for tracking the auction result on events
*/
data class BannerResponse(
val id: String,
val type: EntityType,
val url: String,
val resolvedBidId: String,
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.topsort.analytics.banners

import com.topsort.analytics.model.auctions.Auction
import com.topsort.analytics.model.auctions.AuctionRequest
import com.topsort.analytics.service.TopsortAuctionsHttpService

/**
* Run a banner auction with a single slot
*
* @param config the banner configuration that specifies which kind of banner auction to run
* @return A BannerResponse if the auction successfully returned a winner or null if not.
*/
fun runBannerAuction(config: BannerConfig): BannerResponse? {
val auction = buildBannerAuction(config)
val request = AuctionRequest(listOf(auction))
val response = TopsortAuctionsHttpService.runAuctions(request)
if ((response?.results?.isNotEmpty() == true)) {
if (response.results[0].winners.isNotEmpty()) {
val winner = response.results[0].winners[0]
return BannerResponse(
id = winner.id,
url = winner.asset!!.url,
type = winner.type,
resolvedBidId = winner.resolvedBidId
)
}
}
return null
}

/**
* Builds a low-leve Auction object to be run with TopsortAuctionHttpService.
*
* Generally, you shouldn't be calling this function yourself and you should use runBannerAuction instead.
*
* @param config the banner configuration that specifies which kind of banner auction to run
* @return an Auction object
*/
fun buildBannerAuction(config: BannerConfig): Auction {
when (config) {
is BannerConfig.LandingPage -> {
return Auction.Factory.buildBannerAuctionLandingPage(
1,
config.slotId,
config.ids,
config.device,
config.geoTargeting
)
}

is BannerConfig.CategorySingle -> {
return Auction.Factory.buildBannerAuctionCategorySingle(
1,
config.slotId,
config.category,
config.device,
config.geoTargeting
)
}

is BannerConfig.CategoryMultiple -> {
return Auction.Factory.buildBannerAuctionCategoryMultiple(
1,
config.slotId,
config.categories,
config.device,
config.geoTargeting
)
}

is BannerConfig.CategoryDisjunctions -> {
return Auction.Factory.buildBannerAuctionCategoryDisjunctions(
1, config.slotId, config.disjunctions, config.device, config.geoTargeting
)
}

is BannerConfig.Keyword -> {
return Auction.Factory.buildBannerAuctionKeywords(
1,
config.slotId,
config.keyword,
config.device,
config.geoTargeting
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.topsort.analytics.model.auctions
import org.json.JSONObject

data class AuctionResponse private constructor(
val results : List<AuctionResponseItem>? = null,
val results: List<AuctionResponseItem>,
) {
companion object {
fun fromJson(json: String?): AuctionResponse? {
if(json == null) return null
if (json == null) return null
val array = JSONObject(json).getJSONArray("results")
val results = (0 until array.length()).map {
AuctionResponseItem.fromJsonObject(array.getJSONObject(it))
Expand All @@ -20,11 +20,11 @@ data class AuctionResponse private constructor(
}

data class AuctionResponseItem(
val resultType : String,
val winners : List<AuctionWinnerItem>? = null,
val resultType: String,
val winners: List<AuctionWinnerItem>,
val error: Boolean,
) {
companion object{
companion object {
fun fromJsonObject(json: JSONObject): AuctionResponseItem {
val array = json.getJSONArray("winners")
val winners = (0 until array.length()).map {
Expand All @@ -40,20 +40,49 @@ data class AuctionResponse private constructor(
}

data class AuctionWinnerItem(
val rank : Int,
val type : String,
val id : String,
val rank: Int,
val type: EntityType,
val id: String,
val resolvedBidId: String,
){
companion object{
val asset: Asset? = null,
) {
companion object {
fun fromJsonObject(json: JSONObject): AuctionWinnerItem {
return AuctionWinnerItem(
rank = json.getInt("rank"),
type = json.getString("type"),
id = json.getString("id"),
resolvedBidId = json.getString("resolvedBidId"),
rank = json.getInt("rank"),
type = EntityType.fromValue(json.getString("type")),
id = json.getString("id"),
resolvedBidId = json.getString("resolvedBidId"),
asset = Asset.fromJsonObject(json),
)
}
}
}

data class Asset(val url: String) {
companion object {
fun fromJsonObject(json: JSONObject): Asset? {
val asset = json.optJSONObject("asset") ?: return null
val url = asset.getString("url")
return Asset(url = url)
}
}
}
}

enum class EntityType {
PRODUCT,
VENDOR,
BRAND,
URL;

companion object {
fun fromValue(value: String): EntityType = when (value) {
"product" -> PRODUCT
"vendor" -> VENDOR
"brand" -> BRAND
"url" -> URL
else -> throw IllegalArgumentException()
}
}
}

0 comments on commit f80f07d

Please sign in to comment.