solar_same_period_strategy.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from typing import Any
  2. from app.strategies.batch_date import today_yyyymmdd
  3. from app.strategies.base import (
  4. BaseStrategy,
  5. DemandCandidate,
  6. GenerateContext,
  7. StrategySkipDecision,
  8. )
  9. from app.strategies.odps.solar_same_period_demands import query_solar_same_period_demands
  10. from app.strategies.staging_store import count_staging_batch, has_staging_batch
  11. _NUMERIC_PARAM_KEYS = (
  12. ("view_pv_count", ("view_pv_count",)),
  13. ("min_contribution_score", ("min_contribution_score", "贡献分")),
  14. ("rov_avg", ("rov_avg",)),
  15. )
  16. _OPTIONAL_PARAM_KEYS = (
  17. ("period_days", ("period_days",), 7),
  18. )
  19. class SolarSamePeriodStrategy(BaseStrategy):
  20. """去年同期阳历:取去年同日起到 +6 天(共 7 天)视频窗口聚合实质元素 ROV。"""
  21. strategy_id = "solar_same_period_last_year"
  22. name = "去年同期阳历"
  23. version = "1.0.0"
  24. def validate_config(self, config: dict[str, Any]) -> bool:
  25. try:
  26. self._resolve_params(config)
  27. except (TypeError, ValueError, KeyError):
  28. return False
  29. return True
  30. def should_skip(self, context: GenerateContext) -> StrategySkipDecision:
  31. if not has_staging_batch(
  32. strategy_config_id=self.strategy_id,
  33. batch_date=context.batch_date,
  34. ):
  35. return StrategySkipDecision(skip=False)
  36. existing_count = count_staging_batch(
  37. strategy_config_id=self.strategy_id,
  38. batch_date=context.batch_date,
  39. )
  40. return StrategySkipDecision(
  41. skip=True,
  42. reason="strategy_staging already has data for batch_date",
  43. detail={"existing_count": existing_count},
  44. )
  45. def generate(self, context: GenerateContext) -> list[DemandCandidate]:
  46. params = self._resolve_params(context.params)
  47. rows = query_solar_same_period_demands(
  48. bizdate=today_yyyymmdd(),
  49. period_days=params["period_days"],
  50. view_pv_count=params["view_pv_count"],
  51. min_contribution_score=params["min_contribution_score"],
  52. rov_avg=params["rov_avg"],
  53. )
  54. candidates: list[DemandCandidate] = []
  55. for row in rows:
  56. demand_name = str(row.get("demand_name") or "").strip()
  57. if not demand_name:
  58. continue
  59. weight = row.get("weight")
  60. priority_score = float(weight) if weight is not None else None
  61. video_count = row.get("video_count")
  62. parsed_video_count = int(video_count) if video_count is not None else None
  63. candidates.append(
  64. DemandCandidate(
  65. content=demand_name,
  66. priority_score=priority_score,
  67. demand_id=str(row["demand_id"]) if row.get("demand_id") else None,
  68. demand_type=str(row.get("type") or "特征点"),
  69. video_count=parsed_video_count,
  70. video_list=row.get("video_list"),
  71. )
  72. )
  73. return candidates
  74. @staticmethod
  75. def _pick_param(config: dict[str, Any], keys: tuple[str, ...]) -> Any:
  76. for key in keys:
  77. if key in config:
  78. return config[key]
  79. raise KeyError(keys[0])
  80. @classmethod
  81. def _resolve_params(cls, config: dict[str, Any]) -> dict[str, int | float]:
  82. resolved: dict[str, int | float] = {}
  83. for canonical, aliases in _NUMERIC_PARAM_KEYS:
  84. raw = cls._pick_param(config, aliases)
  85. if canonical == "view_pv_count":
  86. value = int(raw)
  87. if value < 0:
  88. raise ValueError(f"{canonical} 不能为负")
  89. resolved[canonical] = value
  90. else:
  91. value = float(raw)
  92. if value < 0:
  93. raise ValueError(f"{canonical} 不能为负")
  94. resolved[canonical] = value
  95. for canonical, aliases, default in _OPTIONAL_PARAM_KEYS:
  96. raw = default
  97. for key in aliases:
  98. if key in config:
  99. raw = config[key]
  100. break
  101. value = int(raw)
  102. if value < 1:
  103. raise ValueError(f"{canonical} 须 >= 1")
  104. resolved[canonical] = value
  105. return resolved