utils.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. from django.conf import settings
  5. from typing import TYPE_CHECKING
  6. from apps.web.ad.models import Advertisement, Advertiser
  7. from apps.web.core.mathematics import get_haversine_by_km
  8. if TYPE_CHECKING:
  9. from apps.web.user.models import MyUser
  10. logger = logging.getLogger(__name__)
  11. AD_SET_KEY = 'ads'
  12. INDEX_AD_RECORD_TIMESTAMP = 'idx:ad-record:timestamp'
  13. #: (logicalCode) -> ZSET[adIds]
  14. def device_ad_match_set_key(logicalCode):
  15. # type:(str)->str
  16. return 'device:%s:ad-matched' % (logicalCode,)
  17. #: (openId, adId) -> adRecordKey
  18. ad_record_key = lambda openId, adId: 'user:%s:adId:%s' % (openId, adId)
  19. ad_key = lambda adId: 'ad:adId:%s' % (adId,)
  20. user_ad_converted_set_key = lambda openId: 'user:%s:ad-converted' % (openId,)
  21. user_ad_view_key = lambda openId: 'user:%s:ad-viewed' % (openId,)
  22. user_ad_match_key = lambda openId: 'user:%s:ad-matched' % (openId,)
  23. #: 广告关联集合,记录其与代理商,经销商,组,设备的关系
  24. AD_ASSOC_AGENT_UNION = 'ad-assoc:agent'
  25. AD_ASSOC_DEALER_UNION = 'ad-assoc:dealer'
  26. AD_ASSOC_GROUP_UNION = 'ad-assoc:group'
  27. AD_ASSOC_DEVICE_UNION = 'ad-assoc:device'
  28. AD_ASSOC = [AD_ASSOC_AGENT_UNION, AD_ASSOC_DEALER_UNION, AD_ASSOC_GROUP_UNION, AD_ASSOC_DEVICE_UNION]
  29. def consume_advertiser_quota(advertiserId, consumed_quota=1):
  30. # type: (str, int) -> None
  31. if not advertiserId:
  32. logger.info('advertiserId is null, skipped consuming quota')
  33. return
  34. advertiser = Advertiser.objects(id=advertiserId).get()
  35. updated = advertiser.update(dec__quota=consumed_quota)
  36. logger.info('{0!r} updated quota({1!r}), updated={2}'.format(advertiser, consumed_quota, updated))
  37. if advertiser.reload().quota == 0:
  38. status_updated = Advertisement.objects(advertiserId=advertiserId).update(status=False)
  39. logger.info('{0!r}\'s quota reached 0. set all his ads\' status to be False, updated={1}'
  40. .format(advertiser, status_updated))
  41. #: math
  42. def cpc_to_ecpm(views, clicks, cpc):
  43. return 1000. * cpc * clicks / views
  44. def cpa_to_ecpm(views, actions, cpa):
  45. return 1000. * cpa * actions / views
  46. def calc_user_device_distance(device, user):
  47. # type: (dict, MyUser) -> int
  48. user_location = user.locations.pop().coordinates
  49. device_location = (device['lng'], device['lat'])
  50. distance = get_haversine_by_km(user_location, device_location)
  51. return distance
  52. def user_near_device(device, user):
  53. # type: (dict, MyUser) -> bool
  54. """
  55. 检测是否用户靠近设备,目前只与广告相关
  56. :param device:
  57. :param user:
  58. :return:
  59. """
  60. if not settings.AD_LOCATION_LIMIT:
  61. return True
  62. if not len(user.locations):
  63. logger.debug('no user({0!r}) location available'.format(user))
  64. return False
  65. else:
  66. distance = calc_user_device_distance(device, user)
  67. if distance > settings.MAX_DISTANCE_TO_SHOW_AD:
  68. logger.debug('user({0!r}) is too far from the device(logicalCode={1})'.format(user, device['logicalCode']))
  69. return False
  70. else:
  71. return True
  72. def get_available_ads_by_user(device, user):
  73. # type: (dict, MyUser) -> list
  74. """
  75. 在设备侧准备好的广告用于匹配用户的特征
  76. :param device:
  77. :param user:
  78. :return:
  79. """
  80. logger.debug('getting available ads by user, device=(logicalCode=%s), user=%s'
  81. % (device['logicalCode'], repr(user)))
  82. if not all([device, user]):
  83. return []
  84. else:
  85. return []
  86. def user_has_available_ads(device, user):
  87. # type: (dict, MyUser) -> bool
  88. """
  89. 支付前广告
  90. 为了不影响业务主流程,这里catch所有的异常
  91. :param device:
  92. :param user:
  93. :return:
  94. """
  95. return False