def createTag(tag_id, site_id, status = 'A', sizes = [], imp_track_pixel = "", passback = "", passback_type = "", flags = 0, marketplace = 'O', adjustment = decimal2orb(1.0), tag_pricings = [TagPricingInfo("gn", # country_code 'D', # ccg_type 'M', # ccg_rate_type 1, # site_rate_id decimal2orb(0.0001),# imp_revenue decimal2orb(0.0) # revenue_share )], accepted_categories = [], rejected_categories = [], allow_expandable = False, tokens = [], hidden_tokens = [], passback_tokens = [], template_tokens = [], auction_max_ecpm_share = decimal2orb(1.0), auction_prop_probability_share = decimal2orb(0.0), auction_random_share = decimal2orb(0.0), tag_pricings_timestamp = time2orb(currentTime()), timestamp = time2orb(currentTime())): return TagInfo( tag_id, site_id, status, sizes, imp_track_pixel, passback, passback_type, flags, marketplace, adjustment, tag_pricings , accepted_categories, rejected_categories, allow_expandable, tokens, hidden_tokens, passback_tokens, template_tokens, auction_max_ecpm_share, auction_prop_probability_share, auction_random_share, tag_pricings_timestamp, timestamp)
def __prepareConfig(self): now = currentTime() self.checkTimeStamp = None self.startTriggerId = 4 self.simple_channels = [ SimpleChannelKey( 12, # channel_id "gn", # country_code "", # language "A", # status 1, # behav_param_list_id "", # str_behav_param_list_id [], # categories 1, # threshold 0, # discover [], # page_triggers [], # search_triggers [], # url_triggers [], # url_keyword_triggers time2orb(now) # timestamp ) ] self.behav_params = [ BehavParamInfo( 1, # id 0, # threshold time2orb(now), # timestamp # bp_seq [ BehavParameter( 1, # min_visits 0, # time_from 0, # time_to 1, # weight "P" # trigger_type ) ]) ] self.channelById = [ ChannelById( 12, # channel_id # hard_words self.__generateHardWord(0x04), # PAGE TYPE time2orb(now) # stamp ) ]
def testNormalReconnect(self): 'normal reconnect' self.channelById[0].stamp = time2orb(currentTime()) self.checkFlag.set() self.checkCallSequence(expCalls=[ CallSequence('CampaignServer_chsv_simple_channels', 20), 'ChannelProxy_check', 'ChannelProxy_update_all_ccg', 'ChannelProxy_update_triggers' ], timeout=6)
def testDelayedUpdate(self): 'ChannelProxy update delayed' self.updateTriggersWrapper.wait() checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(self.checkTimeStamp), [], False)) self.assertEqual(self.checkTimeStamp, orb2time(checkData.master_stamp)) result = self.channelServerObj.update_triggers([13, 14]) self.assertEqual(0, len(result.channels), 'channel size') self.updateTriggersWrapper.proceed() self.checkCallSequence(expCalls=['ChannelProxy_update_triggers'], timeout=10)
def ChannelProxy_check(self, query): tlog(10, "ChannelProxy.check(colo=%d, version='%s', timestamp='%s', new_ids=%s)" % \ (query.colo_id, query.version, time2str(orb2time(query.master_stamp)), query.new_ids)) def channel2version(channel): return ChannelVersion( channel.channel_id, reduce(lambda x, y: x+y, map(lambda x: len(x.trigger), channel.words), 0) - \ (len(channel.words) * 3), channel.stamp) return CheckData( time2orb(self.first_stamp), # first_stamp time2orb(currentTime()), # master_stamp # versions (trigger versions sequence) map(channel2version, self.channelById), self.specialTrack, # special_track self.specialAdv, # special_adv self.SourceID, # source_id time2orb(currentTime()) # max_time )
def __prepareConfig(self): self.simple_channels = [ SimpleChannelKey( 11, # channel_id "GN", # country_code "", # language "A", # status 1, # behav_param_list_id "", # str_behav_param_list_id [], # categories 0, # threshold 0, # discover [], # page_triggers [], # search_triggers [], # url_triggers [], # url_keyword_triggers time2orb(currentTime()) # timestamp ) ] self.behav_params = [ BehavParamInfo(1, 0, time2orb(currentTime()), [BehavParameter(1, 0, 0, 1, "P")]) ] self.channelById = [ ChannelById( 11, # channel_id # page_words [ TriggerInfo( 1, ChannelServerUtils.createChannelTriggerWord( 'P', "word")) ], time2orb(currentTime()) # stamp ) ]
def test1stTriggerActivation(self): '1st trigger activation' self.updateTriggersWrapper.wait() checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(self.checkTimeStamp), [], False)) self.assertEqual(self.checkTimeStamp, orb2time(checkData.master_stamp)) result = self.channelServerObj.update_triggers([13, 14]) tlog(10, "ChannelServer.update_triggers([13,14]): %s" % result) self.assertEqual(1, len(result.channels), 'channel size') self.assertEqual(13, result.channels[0].channel_id, 'channel#1.id') self.assertNotEqual(None, orb2time(result.channels[0].stamp), 'channel#1.stamp') self.assertNotEqual(1, len(result.channels[0].words), 'channel#1.triggers') self.updateTriggersWrapper.proceed() self.checkCallSequence(expCalls=['ChannelProxy_update_triggers'], timeout=10)
def testDelayedSimpleChannels(self): 'CampaignServer simple_channels delayed' tlog(10, "Calls: %s" % self.calls.queue) self.checkLastCall('CampaignServer_chsv_simple_channels', timeout=2) checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(), [], False)) self.assertNotEqual(self.checkTimeStamp, orb2time(checkData.master_stamp), 'CheckData.master_stamp') self.checkTimeStamp = orb2time(checkData.master_stamp) self.assertEqual(False, checkData.special_track, 'CheckData.special_track') self.assertEqual(False, checkData.special_adv, 'CheckData.special_adv') result = self.channelServerObj.update_triggers([13, 14]) self.assertEqual(0, len(result.channels), 'channel size') self.simpleChannelWrapper.proceed() tlog(10, "Calls: %s" % self.calls.queue)
def testDelayedCheck(self): 'ChannelProxy check delayed' self.checkWrapper.wait() checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(self.checkTimeStamp), [], False)) self.assertEqual(self.checkTimeStamp, orb2time(checkData.master_stamp), 'CheckData.master_stamp') self.assertEqual(False, checkData.special_track, 'CheckData.special_track') self.assertEqual(False, checkData.special_adv, 'CheckData.special_adv') result = self.channelServerObj.update_triggers([13, 14]) self.assertEqual(0, len(result.channels), 'channel size') self.checkWrapper.proceed() self.checkCallSequence(expCalls=[ CallSequence('CampaignServer_chsv_simple_channels', 19), 'ChannelProxy_check', 'ChannelProxy_update_all_ccg' ]) tlog(10, "Calls: %s" % self.calls.queue)
def testPrepare(self): '1st full update withot delays' self.checkCallSequence(expCalls=[ CallSequence('CampaignServer_chsv_simple_channels', 20), 'ChannelProxy_check', 'ChannelProxy_update_all_ccg', 'ChannelProxy_update_triggers' ], timeout=2) self.ChannelServer.waitReady() checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(), [], False)) self.assertNotEqual(None, orb2time(checkData.master_stamp)) result = self.channelServerObj.update_triggers([12]) self.assertEqual(1, len(result.channels), 'channel size') self.assertEqual(12, result.channels[0].channel_id, 'channel#1.id') self.assertNotEqual(None, orb2time(result.channels[0].stamp), 'channel#1.stamp') self.assertNotEqual(1, len(result.channels[0].words), 'channel#1.triggers') self.__prepareDelay() tlog(10, "Calls: %s" % self.calls.queue)
def test2ndTriggerActivation(self): '2nd trigger activation' self.checkCallSequence( expCalls=[CallSequence('CampaignServer_chsv_simple_channels', 20)], timeout=10) checkData = self.channelServerObj.check( CheckQuery(1, "1", time2orb(self.checkTimeStamp), [], False)) self.assertNotEqual(self.checkTimeStamp, orb2time(checkData.master_stamp)) self.channelServerObj.update_triggers([13, 14]) result = self.channelServerObj.update_triggers([13, 14]) tlog(10, "ChannelServer.update_triggers([13,14]): %s" % result) self.assertEqual(2, len(result.channels), 'channel size') self.assertEqual(13, result.channels[0].channel_id, 'channel#1.id') self.assertEqual(14, result.channels[1].channel_id, 'channel#2.id') self.assertNotEqual(None, orb2time(result.channels[0].stamp), 'channel#1.stamp') self.assertNotEqual(None, orb2time(result.channels[1].stamp), 'channel#2.stamp') self.assertNotEqual(1, len(result.channels[0].words), 'channel#1.triggers') self.assertNotEqual(1, len(result.channels[1].words), 'channel#2.triggers')
def createSite(site_id, account_id, status = 'A', freq_cap_id = 0, noads_timeout = 0, approved_creative_categories = [], rejected_creative_categories = [], approved_creatives = [], rejected_creatives = [], flags = 0, timestamp = time2orb(currentTime()) ): return SiteInfo( site_id, status, freq_cap_id, noads_timeout, approved_creative_categories, rejected_creative_categories, approved_creatives, rejected_creatives, flags, account_id, timestamp)
def CampaignServer_get_config(self, get_config_settings): tlog(10, "CampaignServer.get_config") config = CampaignConfigUpdateInfo( self.server_id, # server_id time2orb(currentTime()), # master_stamp time2orb(currentTime()), # first_load_stamp time2orb(currentTime()), # finish_load_stamp time2orb(currentTime()), # current_time self.global_freq_cap_id, self.currency_exchange_id, self.max_keyword_ecpm, self.tanx_publisher_account_id, self.google_publisher_account_id, time2orb(currentTime()), # fraud_user_deactivate_period decimal2orb(self.cost_limit), time2orb(currentTime()), # global_params_timestamp [], # self.app_formats, [], # self.delete_app_formats, [], # self.sizes [], # self.delete_sizes [], # self.accounts, [], # self.deleted_accounts, [], # self.activate_creative_options, [], # self.delete_creative_options, [], # self.campaigns, [], # self.deleted_campaigns, [], # self.ecpms, [], # self.deleted_ecpms, [], # self.sites, [], # self.deleted_sites, [], # self.tags, [], # self.deleted_tags, [], # self.frequency_caps, [], # self.deleted_frequency_caps, [], # self.simple_channels, [], # self.deleted_simple_channels, time2orb(currentTime()), # geo_channels_timestamp [], # self.activate_geo_channels [], # self.delete_geo_channels [], # self.activate_geo_coord_channels [], # self.delete_geo_coord_channels [], # self.activate_block_channels [], # self.delete_block_channels [], # self.colocations, [], # self.deleted_colocations, [], # self.creative_templates, [], # self.deleted_creative_templates, [], # self.currencies, [], # self.expression_channels, [], # self.deleted_expression_channels, [], # self.campaign_keywords, [], # self.deleted_keywords, [], # self.creative_categories, [], # self.deleted_creative_categories, [], # self.adv_actions, [], # self.deleted_adv_actions, [], # self.category_channels, [], # self.deleted_category_channels, [], # self.behav_params, [], # self.deleted_behav_params, [], # self.key_behav_params, [], # self.deleted_key_behav_params, [], # self.fraud_conditions, [], # self.deleted_fraud_conditions, [], # self.search_engines, [], # self.deleted_search_engines, [], # self.web_browsers, [], # self.deleted_web_browsers, [], # self.platforms, [], # self.deleted_platforms, [], # self.string_dictionaries, []) # self.delete_string_dictionaries if get_config_settings.portion == 0: config = CampaignConfigUpdateInfo( self.server_id, # server_id time2orb(currentTime()), # master_stamp time2orb(currentTime()), # first_load_stamp time2orb(currentTime()), # finish_load_stamp time2orb(currentTime()), # current_time self.global_freq_cap_id, self.currency_exchange_id, self.max_keyword_ecpm, self.tanx_publisher_account_id, self.google_publisher_account_id, time2orb(currentTime()), # fraud_user_deactivate_period decimal2orb(self.cost_limit), time2orb(currentTime()), # global_params_timestamp self.app_formats, self.delete_app_formats, self.sizes, self.delete_sizes, self.accounts, self.deleted_accounts, self.activate_creative_options, self.delete_creative_options, self.campaigns, self.deleted_campaigns, self.ecpms, self.deleted_ecpms, self.sites, self.deleted_sites, self.tags, self.deleted_tags, self.frequency_caps, self.deleted_frequency_caps, self.simple_channels, self.deleted_simple_channels, time2orb(currentTime()), # geo_channels_timestamp self.activate_geo_channels, self.delete_geo_channels, self.activate_geo_coord_channels, self.delete_geo_coord_channels, self.activate_block_channels, self.delete_block_channels, self.colocations, self.deleted_colocations, self.creative_templates, self.deleted_creative_templates, self.currencies, self.expression_channels, self.deleted_expression_channels, self.campaign_keywords, self.deleted_keywords, self.creative_categories, self.deleted_creative_categories, self.adv_actions, self.deleted_adv_actions, self.category_channels, self.deleted_category_channels, self.behav_params, self.deleted_behav_params, self.key_behav_params, self.deleted_key_behav_params, self.fraud_conditions, self.deleted_fraud_conditions, self.search_engines, self.deleted_search_engines, self.web_browsers, self.deleted_web_browsers, self.platforms, self.deleted_platforms, self.string_dictionaries, self.delete_string_dictionaries) tlog(10, "CampaignServer.config: %s" % config) return config
def setUp(self): self.server_id = 1 self.global_freq_cap_id = 0 self.currency_exchange_id = 0 self.max_keyword_ecpm = 0 self.tanx_publisher_account_id = 0 self.google_publisher_account_id = 0 self.app_formats = [] self.delete_app_formats = [] self.sizes = [] self.delete_sizes = [] self.accounts = [] self.deleted_accounts = [] self.activate_creative_options = [] self.delete_creative_options = [] self.campaigns = [] self.deleted_campaigns = [] self.ecpms = [] self.deleted_ecpms = [] self.sites = [] self.deleted_sites = [] self.tags = [] self.deleted_tags = [] self.frequency_caps = [] self.deleted_frequency_caps = [] self.colocations = [] self.deleted_colocations = [] self.creative_templates = [] self.deleted_creative_templates = [] self.currencies = [] self.discover_channels = [] self.expression_channels = [] self.deleted_expression_channels = [] self.campaign_keywords = [] self.deleted_keywords = [] self.creative_categories = [] self.deleted_creative_categories = [] self.adv_actions = [] self.deleted_adv_actions = [] self.simple_channels = [] self.chsv_channels = [] self.deleted_simple_channels = [] self.category_channels = [] self.deleted_category_channels = [] self.behav_params = [] self.deleted_behav_params = [] self.key_behav_params = [] self.deleted_key_behav_params = [] self.fraud_conditions = [] self.deleted_fraud_conditions = [] self.search_engines = [] self.deleted_search_engines = [] self.web_browsers = [] self.deleted_web_browsers = [] self.platforms = [] self.deleted_platforms = [] self.activate_geo_channels = [] self.delete_geo_channels = [] self.activate_geo_coord_channels = [] self.delete_geo_coord_channels = [] self.activate_block_channels = [] self.delete_block_channels = [] self.string_dictionaries = [] self.delete_string_dictionaries = [] self.cost_limit = 0.0 self.tzOffset = time2orb(0) # Timezone offset self.masterStamp = time2orb(currentTime()) self.passbackDict = {} # tag_id => passback URL self.activateCampaignServer()
def prepareConfig(config): global campaignManager campaignManager = config now = currentTime() config.expression_channels.extend([ ExpressionChannelInfo( 17, # channel_id "ExprChannel#17", # name 1, # account_id, "gn", # country_code 0, # flags 'A', # status 'B', # type True, # is_public "", # language 0, # freq_cap_id 0, # parent_channel_id; time2orb(now), # timestamp "", # discover_query "", # discover_annotation 0, # channel_rate_id decimal2orb(1.0), # imp_revenue decimal2orb(2.0), # click_revenue # expression ExpressionInfo( 'S', # operation 15, # channel_id [] # sub_channels ), 1 # threshold ) ]) config.campaigns.extend([ createCampaign( account_id=1, campaign_id=100, ccg_id=100, mode=1, ccg_type='T', ccg_rate_type='M', expression=ExpressionInfo( '-', # operation 17, # channel_id []), creatives=[ CreativeInfo( 101, # cc id 101, # creative id 0, # fc_id 1, # weight [ CreativeSizeInfo( 1, # size_id 0, # up_expand_space 0, # right_expand_space 0, # down_expand_space 0, # down_expand_space []) # tokens ], "Image", # format OptionValueInfo(0, ""), # click_url OptionValueInfo(104, ""), # html_url 0, # order_set_id [], # categories [OptionValueInfo(102, "http://www.displayurl.com") ], # tokens 'A', # status '' # version_id ) ]) ])
def createRequest(current_time = time2orb(currentTime()), tag_id = 0, ext_tag_id = "", request_id = REQUEST_ID, full_freq_caps = [], full_virtual_freq_caps = [], seq_orders = [], location = [ GeoInfo( "gn", # country_code "", # region "" # city ) ], coord_location = [], colo_id = 1, format = "unit-test", household_id = USER_ID, user_id = USER_ID, signed_user_id = "", merged_user_id = USER_ID, track_user_id = "", tag_inventory = False, user_status = US_OPTIN, user_agent = "Unknown", referer_hash = 0, client = "Test client", client_version = "1.0", peer_ip = "192.168.1.1", platform_ids = [], platform = "Win", full_platform = "Win 5.1 PI=2", web_browser = "Mozilla 5.0", cohort = '', # current value from cookies, hpos = 256, # UNDEFINED_PUB_POSITION_BOTTOM search_engine_id = 0, page_keywords_present = True, profiling_available = True, fraud = False, test_request = False, log_as_test = True, disable_fraud_detection = False, enabled_notice = False, fill_track_pixel = False, fill_iurl = False, random = 0, original_url = "http://www.test.ocslab.com", channels = [], hid_channels = [], geo_channels = [], ccg_keywords = [], hid_ccg_keywords = [], publisher_account_id = 0, publisher_site_id = 0, request_type = 1, # AR_NORMAL external_user_id = "", source_id = "", creative_instantiate_type = "unsecure", trigger_match_result = TriggerMatchResult([],[],[],[], []), client_create_time = time2orb(currentTime()), client_last_request = time2orb(currentTime()), session_start = time2orb(currentTime()), exclude_pubpixel_accounts = [], required_passback = False, profile_referer = False, ip_hash = "", tag_delivery_factor = 0, page_load_id = 0, preview_ccid = 0, request_token = "", preclick_url = "", click_prefix_url = "", impr_track_url = "", search_words = "", page_keywords = "", url_keywords = "", debug_ccg = 0, passback_type = "", passback_url = "", need_debug_info = True, only_display_ad = True, security_token = "", pub_impr_track_url = "", referer="", urls=[], ad_instantiate_type=1, # url profiling_type = 2 # PT_ALL ): return RequestParams( CommonAdRequestInfo(time2orb(currentTime()), request_id, creative_instantiate_type, request_type, random, test_request, log_as_test, colo_id, publisher_account_id, publisher_site_id, external_user_id, source_id, location, coord_location, referer, urls, security_token, pub_impr_track_url, preclick_url, click_prefix_url, original_url, track_user_id, user_id, user_status, signed_user_id, peer_ip, cohort, hpos, passback_type, passback_url), enabled_notice, fill_track_pixel, fill_iurl, ad_instantiate_type, current_time, only_display_ad, full_freq_caps, full_virtual_freq_caps, seq_orders, household_id, merged_user_id, tag_inventory, user_agent, referer_hash, client, client_version, platform_ids, platform, full_platform, web_browser, search_engine_id, search_words, page_keywords_present, profiling_available, fraud, channels, hid_channels, geo_channels, ccg_keywords, hid_ccg_keywords, trigger_match_result, client_create_time, session_start, exclude_pubpixel_accounts, tag_delivery_factor, page_load_id, preview_ccid, [AdSlotInfo(1, # ad_slot_id; format, #format tag_id, # tag_id; "", # size ext_tag_id, # ext_tag_id; decimal2orb(0.0), # min_ecpm "USD", # min_ecpm_currency_code False, # passback 0, # up_expand_space; 0, # right_expand_space; 0, # left_expand_space; 0, # tag_visibility; 0, # down_expand_space; 0, # video_min_duration; 2147483647, # video_max_duration; [], # exclude_categories debug_ccg, # debug_ccg, [] # allowed_durations )], required_passback, profile_referer, ip_hash, profiling_type, disable_fraud_detection, need_debug_info, page_keywords, url_keywords)
def createCampaign( campaign_id, ccg_id = 1, ccg_rate_id = 1, ccg_rate_type = 'A', fc_id = 0, group_fc_id = 0, priority = 0, flags = 32768, marketplace = 'A', expression = ExpressionInfo( '-', # operation 16, # channel_id []), # sub_channels stat_expression = ExpressionInfo( '-', # operation 16, # channel_id []), # sub_channels country = "gn", sites = [1], status = 'A', eval_status = 'A', dailyrun_intervals = [], creatives = [], account_id = 1, advertiser_id = 1, exclude_pub_accounts = [], exclude_tags= [], imp_revenue = decimal2orb(0.5), click_revenue = decimal2orb(2.0), action_revenue = decimal2orb(3.0), commision = decimal2orb(0.0), ccg_type = 'D', target_type = 'C', campaign_delivery_limits = CampaignDeliveryLimitsInfo( time2orb(yesterday), # date_start time2orb(tommorow), # date_end decimal2orb(1000.0), # budget decimal2orb(1000.0), # daily_budget 'A'), # delivery_pacing ccg_delivery_limits = CampaignDeliveryLimitsInfo( time2orb(yesterday), # date_start time2orb(tommorow), # date_end decimal2orb(1000.0), # budget decimal2orb(1000.0), # daily_budget 'A'), # delivery_pacing start_user_group_id = 0, end_user_group_id = 99, max_pub_share = decimal2orb(1000.0), ctr_reset_id = 0, random_imps = 0, mode = 1, seq_set_rotate_imps = 0, min_uid_age = time2orb(0), colocations = [], timestamp = time2orb(now)): return CampaignInfo( campaign_id, ccg_id, ccg_rate_id, ccg_rate_type, fc_id, group_fc_id, priority, flags, marketplace, expression, stat_expression, country, sites, status, eval_status, dailyrun_intervals, creatives, account_id, advertiser_id, exclude_pub_accounts, exclude_tags, imp_revenue, click_revenue, action_revenue, commision, ccg_type, target_type, campaign_delivery_limits, ccg_delivery_limits, start_user_group_id, end_user_group_id, max_pub_share, ctr_reset_id, random_imps, mode, seq_set_rotate_imps, min_uid_age, colocations, timestamp )
def __prepareDelay(self): now = currentTime() # CampaignServer.simple_channels wrapper self.simpleChannelWrapper = DelayCallWrapper( self, 'CampaignServer_chsv_simple_channels') self.simpleChannelWrapper.wait() # ChannelProxy.check wrapper self.checkWrapper = DelayCallWrapper(self, 'ChannelProxy_check') # ChannelProxy.update_triggers wrapper self.updateTriggersWrapper = DelayCallWrapper( self, 'ChannelProxy_update_triggers', 2) # Change config self.simple_channels += [ SimpleChannelKey( 13, # channel_id "gn", # country_code "", # language "A", # status 1, # behav_param_list_id "", # str_behav_param_list_id [], # categories 1, # threshold 0, # discover [], # page_triggers [], # search_triggers [], # url_triggers [], # url_keyword_triggers time2orb(now) # timestamp ), SimpleChannelKey( 14, # channel_id "gn", # country_code "", # language "A", # status 1, # behav_param_list_id "", # str_behav_param_list_id [], # categories 1, # threshold 0, # discover [], # page_triggers [], # search_triggers [], # url_triggers [], # url_keyword_triggers time2orb(now) # timestamp ) ] self.channelById += [ ChannelById( 13, # channel_id # hard_words self.__generateHardWord(0x04), # PAGE TYPE time2orb(now) # stamp ), ChannelById( 14, # channel_id # hard_words self.__generateHardWord(0x04), # PAGE TYPE time2orb(now) # stamp ) ]
def CampaignServer_detectors(self, request_timestamp): tlog( 10, "CampaignServer.search_engines %s" % time2str(orb2time(request_timestamp))) return DetectorsConfig(time2orb(currentTime()), self.search_engines, self.web_browsers, self.platforms)