import { Campaign, ChoiceRequest, CampaignGroup, RequestType } from "../models"; import MetricService, { BucketSet } from "../services/metric.service"; import { config } from "../utils/config"; import logger from "../utils/logger"; import { delivery } from "../protos/protos"; const cache = require("memory-cache"); const MS_YIELD_RESPONSE_METRIC = "ms_yield_response_time"; const YIELD_CACHE_TTL = 180000; const bypassingEnvs = ["staging", "integration_test", "devc", "local"]; export default class YieldService { private campaignsClient: delivery.Campaigns; private cache; private metricService: MetricService; constructor(campaignsClient: delivery.Campaigns) { this.metricBucket = BucketSet.zeroTo25ms; this.campaignsClient = campaignsClient; this.cache = new cache.Cache(); this.metricService = MetricService.getInstance(); this.metricService.addSection(MS_YIELD_RESPONSE_METRIC); } buildCacheKey( adUnitId: string | null | undefined, country: string | null | undefined, campaigns: Campaign[] ): string { // format: $adUnit_$country_$campaign1_$campaign2 ... const keyBase = `${adUnitId}_${country}`; return campaigns .map((c) => parseInt(c.id)) .sort((c1, c2) => c1 - c2) .reduce((acc, value) => `${acc}_${value}`, keyBase); } setResponseCache(key: string, value: delivery.RecommenderResponse, ttl: number) { this.cache.put(key, value, ttl); } getResponseCache(key: string): delivery.RecommenderResponse | null { const value = this.cache.get(key); return value && value.campaigns ? value : null; } clearResponseCache() { this.cache.clear(); } buildMsYieldRequest(request: ChoiceRequest): delivery.Request { const campaignList: delivery.Campaign[] = request.campaigns.map((c) => delivery.Campaign.create({ id: c.id, category_iab: c.category_iab, }) ); const reqCtx = request.context ?? {}; const reqApp = request.app ?? {}; const context = delivery.Context.create({ day_of_week: reqCtx.day_of_week, hour: reqCtx.hour, city: reqCtx.city, ssp: reqCtx.ssp, country: reqCtx.country, }); const app = delivery.App.create({ bundle_id: reqApp.bundle_id, name: reqApp.name, cat: reqApp.cat, app_api_key: `${reqApp.app_api_key}`, ad_unit_id: reqApp.ad_unit_id, app_publisher_id: reqApp.app_publisher_id, eecpm: reqApp.eecpm, }); return delivery.Request.create({ id: request.id, campaigns: campaignList, context, app, }); } async callMsYield(request: ChoiceRequest): Promise { const msYieldReq = this.buildMsYieldRequest(request); const end = this.metricService.startSectionWithReq(request, MS_YIELD_RESPONSE_METRIC); try { const response = await this.campaignsClient.getRecommender(msYieldReq); end(true); return response; } catch (err: any) { end(false, { error_code: err.code, }); throw err; } } async getMsYieldData(request: ChoiceRequest): Promise<[delivery.RecommenderResponse, boolean]> { const cacheKey = this.buildCacheKey( request.app?.ad_unit_id, request.context?.country, request.campaigns ); let msYieldData = this.getResponseCache(cacheKey); const fromCache = !!msYieldData; if (!msYieldData) { msYieldData = await this.callMsYield(request); this.setResponseCache(cacheKey, msYieldData, YIELD_CACHE_TTL); } return [msYieldData, fromCache]; } } // export default YieldStep;