import _ from "lodash"; import { Campaign, ChoiceRequest, CampaignGroup, splitReqCampaignGroups } from "../../models"; import { StepResult } from "../stepResult"; import { AbstractStep } from "../abstractStep"; import { FeatureConfigService } from "../../services/FeatureConfig.service"; import MetricService from "../../services/metric.service"; import constants from "../../utils/constants"; class FallbackStep extends AbstractStep { featureConfigService: FeatureConfigService; metricService: MetricService; constructor() { super("fallback_selected"); this.featureConfigService = FeatureConfigService.getInstance(); this.metricService = MetricService.getInstance(); this.metricService.addHistogram( constants.STEP_FALLBACK_METRIC, "Number of time of fallback pass", ["eligible", "pass"], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 20, 30] ); this.metricService.addCounter( constants.STEP_FALLBACK_METRIC_NO_PAYBACK, "Number of time where payback CPM is not configure on campaign" ); } async run(request: ChoiceRequest): Promise { const result: StepResult = new StepResult(); const [fallbackCampaigns, otherCampaigns] = splitReqCampaignGroups( request, CampaignGroup.Fallback ); if (otherCampaigns.length > 0 || fallbackCampaigns.length === 0) { result.passCampaigns(...otherCampaigns); this.metricService.observeWithReq(request, constants.STEP_FALLBACK_METRIC, 0, { eligible: "false", pass: "false", }); return result; } const filteredFallbackCampaigns = this.filterFallbackCampaignsByMargin( request, fallbackCampaigns ); if (filteredFallbackCampaigns.length === 0) { this.metricService.observeWithReq( request, constants.STEP_FALLBACK_METRIC, request.app?.eecpm ?? 0, { eligible: "true", pass: "false", } ); return result; } for (const c of filteredFallbackCampaigns) { const campaignData = request.campaignData[c.id]; c.metadata.creative_id = campaignData.creatives?.[0]?.id; } result.passCampaigns(..._.shuffle(filteredFallbackCampaigns)); this.metricService.observeWithReq( request, constants.STEP_FALLBACK_METRIC, request.app?.eecpm ?? 0, { eligible: "true", pass: "true", } ); return result; } filterFallbackCampaignsByMargin( request: ChoiceRequest, fallbackCampaigns: Campaign[] ): Campaign[] { let eecpm = 0; let adUnitType: string; if (!request.app?.eecpm || !request.app.ad_unit_type) { return []; } else { eecpm = request.app.eecpm; adUnitType = request.app.ad_unit_type; } return fallbackCampaigns.filter((c) => { const campaignData = request.campaignData[c.id]; if (campaignData?.ad_unit_types && !campaignData?.ad_unit_types[adUnitType]?.payback_cpm) { this.metricService.incCounter(constants.STEP_FALLBACK_METRIC_NO_PAYBACK, 1); return false; } else { const paybackCpm = campaignData?.ad_unit_types ? campaignData?.ad_unit_types[adUnitType]?.payback_cpm ?? -1 : -1; const budgetPerAccomplished = (campaignData.budget ?? 0) / (campaignData.clicks_sold ?? 1); const cta = budgetPerAccomplished * 1000; return Math.min(cta, paybackCpm) >= eecpm; } }); } } export default FallbackStep;