# -*- coding: utf-8 -*- # !/usr/bin/env python import logging from django.conf import settings from typing import TYPE_CHECKING from apps.web.ad.models import Advertisement, Advertiser from apps.web.core.mathematics import get_haversine_by_km if TYPE_CHECKING: from apps.web.user.models import MyUser logger = logging.getLogger(__name__) AD_SET_KEY = 'ads' INDEX_AD_RECORD_TIMESTAMP = 'idx:ad-record:timestamp' #: (logicalCode) -> ZSET[adIds] def device_ad_match_set_key(logicalCode): # type:(str)->str return 'device:%s:ad-matched' % (logicalCode,) #: (openId, adId) -> adRecordKey ad_record_key = lambda openId, adId: 'user:%s:adId:%s' % (openId, adId) ad_key = lambda adId: 'ad:adId:%s' % (adId,) user_ad_converted_set_key = lambda openId: 'user:%s:ad-converted' % (openId,) user_ad_view_key = lambda openId: 'user:%s:ad-viewed' % (openId,) user_ad_match_key = lambda openId: 'user:%s:ad-matched' % (openId,) #: 广告关联集合,记录其与代理商,经销商,组,设备的关系 AD_ASSOC_AGENT_UNION = 'ad-assoc:agent' AD_ASSOC_DEALER_UNION = 'ad-assoc:dealer' AD_ASSOC_GROUP_UNION = 'ad-assoc:group' AD_ASSOC_DEVICE_UNION = 'ad-assoc:device' AD_ASSOC = [AD_ASSOC_AGENT_UNION, AD_ASSOC_DEALER_UNION, AD_ASSOC_GROUP_UNION, AD_ASSOC_DEVICE_UNION] def consume_advertiser_quota(advertiserId, consumed_quota=1): # type: (str, int) -> None if not advertiserId: logger.info('advertiserId is null, skipped consuming quota') return advertiser = Advertiser.objects(id=advertiserId).get() updated = advertiser.update(dec__quota=consumed_quota) logger.info('{0!r} updated quota({1!r}), updated={2}'.format(advertiser, consumed_quota, updated)) if advertiser.reload().quota == 0: status_updated = Advertisement.objects(advertiserId=advertiserId).update(status=False) logger.info('{0!r}\'s quota reached 0. set all his ads\' status to be False, updated={1}' .format(advertiser, status_updated)) #: math def cpc_to_ecpm(views, clicks, cpc): return 1000. * cpc * clicks / views def cpa_to_ecpm(views, actions, cpa): return 1000. * cpa * actions / views def calc_user_device_distance(device, user): # type: (dict, MyUser) -> int user_location = user.locations.pop().coordinates device_location = (device['lng'], device['lat']) distance = get_haversine_by_km(user_location, device_location) return distance def user_near_device(device, user): # type: (dict, MyUser) -> bool """ 检测是否用户靠近设备,目前只与广告相关 :param device: :param user: :return: """ if not settings.AD_LOCATION_LIMIT: return True if not len(user.locations): logger.debug('no user({0!r}) location available'.format(user)) return False else: distance = calc_user_device_distance(device, user) if distance > settings.MAX_DISTANCE_TO_SHOW_AD: logger.debug('user({0!r}) is too far from the device(logicalCode={1})'.format(user, device['logicalCode'])) return False else: return True def get_available_ads_by_user(device, user): # type: (dict, MyUser) -> list """ 在设备侧准备好的广告用于匹配用户的特征 :param device: :param user: :return: """ logger.debug('getting available ads by user, device=(logicalCode=%s), user=%s' % (device['logicalCode'], repr(user))) if not all([device, user]): return [] else: return [] def user_has_available_ads(device, user): # type: (dict, MyUser) -> bool """ 支付前广告 为了不影响业务主流程,这里catch所有的异常 :param device: :param user: :return: """ return False