Selaa lähdekoodia

增加用户画像暂时不判断

xueyiming 4 päivää sitten
vanhempi
commit
8c384ca7d8

+ 2 - 0
.env.example

@@ -71,6 +71,8 @@ FIND_AGENT_MIN_DURATION_SECONDS=30
 FIND_AGENT_MIN_SHARE_COUNT=1000
 FIND_AGENT_MIN_CONTENT_50_PLUS_RATIO=0.20
 FIND_AGENT_MIN_ACCOUNT_50_PLUS_RATIO=0.20
+# 画像接口故障时可设为 true;仅放行双侧画像均缺失,已知画像比例过低仍会拒绝
+FIND_AGENT_ALLOW_MISSING_ELDER_PORTRAIT=false
 FIND_AGENT_FESTIVAL_LEAD_DAYS=7
 FIND_AGENT_EVENT_MAX_AGE_DAYS=7
 FIND_AGENT_SEASONAL_MAX_AGE_DAYS=180

+ 4 - 0
supply_infra/config.py

@@ -142,6 +142,10 @@ class InfraSettings(BaseSettings):
         le=1,
         alias="FIND_AGENT_MIN_ACCOUNT_50_PLUS_RATIO",
     )
+    find_agent_allow_missing_elder_portrait: bool = Field(
+        default=False,
+        alias="FIND_AGENT_ALLOW_MISSING_ELDER_PORTRAIT",
+    )
     find_agent_festival_lead_days: int = Field(
         default=7,
         ge=0,

+ 25 - 9
supply_infra/video_discovery_gates.py

@@ -20,6 +20,7 @@ class FindAgentGateRules:
     min_share_count: int
     min_content_50_plus_ratio: float
     min_account_50_plus_ratio: float
+    allow_missing_elder_portrait: bool
     festival_lead_days: int
     event_max_age_days: int
     seasonal_max_age_days: int
@@ -106,6 +107,9 @@ def get_gate_rules() -> FindAgentGateRules:
         min_share_count=settings.find_agent_min_share_count,
         min_content_50_plus_ratio=settings.find_agent_min_content_50_plus_ratio,
         min_account_50_plus_ratio=settings.find_agent_min_account_50_plus_ratio,
+        allow_missing_elder_portrait=(
+            settings.find_agent_allow_missing_elder_portrait
+        ),
         festival_lead_days=settings.find_agent_festival_lead_days,
         event_max_age_days=settings.find_agent_event_max_age_days,
         seasonal_max_age_days=settings.find_agent_seasonal_max_age_days,
@@ -136,7 +140,11 @@ def load_rule_snapshot(value: Mapping[str, Any] | str | None) -> dict[str, Any]:
     else:
         loaded = dict(value or {})
     fallback = build_rule_snapshot()
-    return {**fallback, **loaded}
+    if value is None:
+        return fallback
+    # 旧运行的规则快照没有此字段时保持原有严格行为,避免环境变量变化
+    # 反向修改已经创建的运行。
+    return {**fallback, "allow_missing_elder_portrait": False, **loaded}
 
 
 def parse_datetime_value(
@@ -594,22 +602,29 @@ def evaluate_candidate_gate(
     account_pass = (
         account_ratio is not None and account_ratio >= min_account_ratio
     )
+    allow_missing_portrait = bool(rules["allow_missing_elder_portrait"])
     portrait_compensated = False
+    portrait_config_bypassed = False
     if content_pass or account_pass:
         portrait_status = "pass"
         portrait_reason_code = None
     elif content_ratio is None and account_ratio is None:
-        portrait_compensated = _can_compensate_missing_portrait(
-            candidate,
-            account_ratio,
-            min_account_ratio=min_account_ratio,
-        )
-        if portrait_compensated:
+        if allow_missing_portrait:
             portrait_status = "pass"
             portrait_reason_code = None
+            portrait_config_bypassed = True
         else:
-            portrait_status = "fail"
-            portrait_reason_code = "CONTENT_PORTRAIT_MISSING"
+            portrait_compensated = _can_compensate_missing_portrait(
+                candidate,
+                account_ratio,
+                min_account_ratio=min_account_ratio,
+            )
+            if portrait_compensated:
+                portrait_status = "pass"
+                portrait_reason_code = None
+            else:
+                portrait_status = "fail"
+                portrait_reason_code = "CONTENT_PORTRAIT_MISSING"
     else:
         portrait_status = "fail"
         portrait_reason_code = "PORTRAIT_50_PLUS_TOO_LOW"
@@ -628,6 +643,7 @@ def evaluate_candidate_gate(
                 "min_account_50_plus_ratio": min_account_ratio,
             },
             "compensated": portrait_compensated,
+            "config_bypassed": portrait_config_bypassed,
         }
     )
 

+ 28 - 0
tests/supply_infra/scheduler/test_discover_videos_from_demands.py

@@ -321,6 +321,7 @@ _P0_RULES = {
     "min_share_count": 1000,
     "min_content_50_plus_ratio": 0.20,
     "min_account_50_plus_ratio": 0.20,
+    "allow_missing_elder_portrait": False,
     "festival_lead_days": 7,
     "event_max_age_days": 7,
     "seasonal_max_age_days": 180,
@@ -425,6 +426,33 @@ def test_p0_gate_keeps_content_and_account_portraits_separate() -> None:
     assert "PORTRAIT_50_PLUS_TOO_LOW" in both_low["failed_reason_codes"]
 
 
+def test_p0_gate_can_allow_fully_missing_elder_portraits_by_config() -> None:
+    missing_candidate = _p0_candidate(
+        content_50_plus_ratio=None,
+        account_50_plus_ratio=None,
+    )
+
+    strict = evaluate_candidate_gate(missing_candidate, _P0_RULES)
+    assert strict["primary_eligible"] is False
+    assert "CONTENT_PORTRAIT_MISSING" in strict["failed_reason_codes"]
+
+    relaxed_rules = {**_P0_RULES, "allow_missing_elder_portrait": True}
+    relaxed = evaluate_candidate_gate(missing_candidate, relaxed_rules)
+    portrait_check = next(
+        check for check in relaxed["checks"] if check["name"] == "elder_portrait"
+    )
+    assert relaxed["primary_eligible"] is True
+    assert portrait_check["status"] == "pass"
+    assert portrait_check["config_bypassed"] is True
+
+    known_low = evaluate_candidate_gate(
+        _p0_candidate(content_50_plus_ratio=0.10, account_50_plus_ratio=None),
+        relaxed_rules,
+    )
+    assert known_low["primary_eligible"] is False
+    assert "PORTRAIT_50_PLUS_TOO_LOW" in known_low["failed_reason_codes"]
+
+
 def test_p0_gate_compensates_missing_fields_with_strong_signals() -> None:
     missing_publish = evaluate_candidate_gate(
         _p0_candidate(publish_at=None, share_count=2000),