|
|
@@ -66,6 +66,7 @@ from tools.creative_creation import ( # noqa: E402
|
|
|
)
|
|
|
from tools.creative_material_usage import ( # noqa: E402
|
|
|
load_recent_landing_usage_counts,
|
|
|
+ load_recoverable_prepared_records,
|
|
|
record_prepared_material_usage,
|
|
|
)
|
|
|
from tools.account_material_strategy import ( # noqa: E402
|
|
|
@@ -460,6 +461,63 @@ def phase1_prepare(target_creatives: int = TARGET_CREATIVES_PER_AD) -> list[dict
|
|
|
# landing_video 按素材来源拆池。历史素材和 AI 生成素材互不占用,但池内都严格去重。
|
|
|
landing_usage_counts_by_crowd: dict[str, dict[str, dict[int, int]]] = {}
|
|
|
|
|
|
+ def accept_pending_record(
|
|
|
+ rec: dict,
|
|
|
+ *,
|
|
|
+ crowd_package: str,
|
|
|
+ display_excluded_material_ids: set[str],
|
|
|
+ crowd_landing_counts: dict[str, dict[int, int]],
|
|
|
+ landing_counts_for_ad: dict[int, int],
|
|
|
+ recovered: bool = False,
|
|
|
+ ) -> None:
|
|
|
+ pending_records.append(rec)
|
|
|
+ material_id = rec.get("_material_id")
|
|
|
+ if material_id:
|
|
|
+ display_excluded_material_ids.add(str(material_id))
|
|
|
+ logger.info(
|
|
|
+ "[phase1] 本轮展示去重登记 crowd=%r material=%s size=%d%s",
|
|
|
+ crowd_package, material_id,
|
|
|
+ len(display_excluded_material_ids),
|
|
|
+ " recovered" if recovered else "",
|
|
|
+ )
|
|
|
+ landing_video_id = rec.get("landing_video_id")
|
|
|
+ if landing_video_id is not None:
|
|
|
+ landing_video_id = int(landing_video_id)
|
|
|
+ landing_counts_for_ad[landing_video_id] = (
|
|
|
+ landing_counts_for_ad.get(landing_video_id, 0) + 1
|
|
|
+ )
|
|
|
+ actual_material_source = (
|
|
|
+ MATERIAL_SOURCE_AI_GENERATED
|
|
|
+ if str(rec.get("material_source") or "") == MATERIAL_SOURCE_AI_GENERATED
|
|
|
+ or str(rec.get("_material_id") or "").startswith("ai:")
|
|
|
+ else MATERIAL_SOURCE_HISTORY
|
|
|
+ )
|
|
|
+ source_counts = crowd_landing_counts.setdefault(actual_material_source, {})
|
|
|
+ source_counts[landing_video_id] = source_counts.get(landing_video_id, 0) + 1
|
|
|
+ logger.info(
|
|
|
+ "[phase1] 同人群包 landing 使用登记 crowd=%r material_source=%s landing=%d count=%d max=%d%s",
|
|
|
+ crowd_package, actual_material_source, landing_video_id,
|
|
|
+ source_counts[landing_video_id],
|
|
|
+ MAX_SAME_LANDING_PER_AD_IN_RUN,
|
|
|
+ " recovered" if recovered else "",
|
|
|
+ )
|
|
|
+ logger.info(
|
|
|
+ "[phase1] 本轮同广告 landing 计数 adgroup=%s landing=%d count=%d limit=%d%s",
|
|
|
+ rec.get("adgroup_id"), landing_video_id,
|
|
|
+ landing_counts_for_ad[landing_video_id],
|
|
|
+ MAX_SAME_LANDING_PER_AD_IN_RUN,
|
|
|
+ " recovered" if recovered else "",
|
|
|
+ )
|
|
|
+ if not recovered:
|
|
|
+ try:
|
|
|
+ record_prepared_material_usage(rec)
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(
|
|
|
+ "[phase1] material usage 记录失败 account=%s adgroup=%s material=%s:%s",
|
|
|
+ rec.get("account_id"), rec.get("adgroup_id"),
|
|
|
+ rec.get("_material_id"), e,
|
|
|
+ )
|
|
|
+
|
|
|
for account_id in creation_accounts:
|
|
|
logger.info("=" * 60)
|
|
|
logger.info("[phase1] 账户 %d 处理开始", account_id)
|
|
|
@@ -547,8 +605,30 @@ def phase1_prepare(target_creatives: int = TARGET_CREATIVES_PER_AD) -> list[dict
|
|
|
"[phase1] adgroup=%d(have=%d need=%d)",
|
|
|
adgroup_id, already_have, to_add,
|
|
|
)
|
|
|
+ recovered_records = load_recoverable_prepared_records(
|
|
|
+ account_id=account_id,
|
|
|
+ adgroup_id=adgroup_id,
|
|
|
+ crowd_package=crowd_package,
|
|
|
+ material_source=requested_material_source,
|
|
|
+ limit=to_add,
|
|
|
+ )
|
|
|
+ for rec in recovered_records:
|
|
|
+ accept_pending_record(
|
|
|
+ rec,
|
|
|
+ crowd_package=crowd_package,
|
|
|
+ display_excluded_material_ids=display_excluded_material_ids,
|
|
|
+ crowd_landing_counts=crowd_landing_counts,
|
|
|
+ landing_counts_for_ad=landing_counts_for_ad,
|
|
|
+ recovered=True,
|
|
|
+ )
|
|
|
+ if recovered_records:
|
|
|
+ prepared_for_ad += len(recovered_records)
|
|
|
+ logger.info(
|
|
|
+ "[phase1] adgroup=%d 恢复未提交 prepared records=%d remaining=%d",
|
|
|
+ adgroup_id, len(recovered_records), max(0, to_add - prepared_for_ad),
|
|
|
+ )
|
|
|
|
|
|
- for _ in range(to_add):
|
|
|
+ for _ in range(max(0, to_add - prepared_for_ad)):
|
|
|
landing_excluded_for_ad = {
|
|
|
vid
|
|
|
for vid, count in landing_counts_for_ad.items()
|
|
|
@@ -578,52 +658,13 @@ def phase1_prepare(target_creatives: int = TARGET_CREATIVES_PER_AD) -> list[dict
|
|
|
|
|
|
if rec:
|
|
|
prepared_for_ad += 1
|
|
|
- pending_records.append(rec)
|
|
|
- material_id = rec.get("_material_id")
|
|
|
- if material_id:
|
|
|
- display_excluded_material_ids.add(str(material_id))
|
|
|
- logger.info(
|
|
|
- "[phase1] 本轮展示去重登记 crowd=%r material=%s size=%d",
|
|
|
- crowd_package, material_id,
|
|
|
- len(display_excluded_material_ids),
|
|
|
- )
|
|
|
- landing_video_id = rec.get("landing_video_id")
|
|
|
- if landing_video_id is not None:
|
|
|
- landing_video_id = int(landing_video_id)
|
|
|
- landing_counts_for_ad[landing_video_id] = (
|
|
|
- landing_counts_for_ad.get(landing_video_id, 0) + 1
|
|
|
- )
|
|
|
- actual_material_source = (
|
|
|
- MATERIAL_SOURCE_AI_GENERATED
|
|
|
- if str(rec.get("material_source") or "") == MATERIAL_SOURCE_AI_GENERATED
|
|
|
- or str(rec.get("_material_id") or "").startswith("ai:")
|
|
|
- else MATERIAL_SOURCE_HISTORY
|
|
|
- )
|
|
|
- source_counts = crowd_landing_counts.setdefault(
|
|
|
- actual_material_source, {},
|
|
|
- )
|
|
|
- source_counts[landing_video_id] = (
|
|
|
- source_counts.get(landing_video_id, 0) + 1
|
|
|
- )
|
|
|
- logger.info(
|
|
|
- "[phase1] 同人群包 landing 使用登记 crowd=%r material_source=%s landing=%d count=%d max=%d",
|
|
|
- crowd_package, actual_material_source, landing_video_id,
|
|
|
- source_counts[landing_video_id],
|
|
|
- MAX_SAME_LANDING_PER_AD_IN_RUN,
|
|
|
- )
|
|
|
- logger.info(
|
|
|
- "[phase1] 本轮同广告 landing 计数 adgroup=%d landing=%d count=%d limit=%d",
|
|
|
- adgroup_id, landing_video_id,
|
|
|
- landing_counts_for_ad[landing_video_id],
|
|
|
- MAX_SAME_LANDING_PER_AD_IN_RUN,
|
|
|
- )
|
|
|
- try:
|
|
|
- record_prepared_material_usage(rec)
|
|
|
- except Exception as e:
|
|
|
- logger.warning(
|
|
|
- "[phase1] material usage 记录失败 account=%d adgroup=%d material=%s:%s",
|
|
|
- account_id, adgroup_id, rec.get("_material_id"), e,
|
|
|
- )
|
|
|
+ accept_pending_record(
|
|
|
+ rec,
|
|
|
+ crowd_package=crowd_package,
|
|
|
+ display_excluded_material_ids=display_excluded_material_ids,
|
|
|
+ crowd_landing_counts=crowd_landing_counts,
|
|
|
+ landing_counts_for_ad=landing_counts_for_ad,
|
|
|
+ )
|
|
|
else:
|
|
|
failed_prepare_for_ad += 1
|
|
|
# 2026-06-10 用户要求:单条 prepare 失败 → continue 不 break
|