|
@@ -1,7 +1,9 @@
|
|
|
from __future__ import annotations
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import json
|
|
import json
|
|
|
|
|
+import hashlib
|
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
|
|
|
+from datetime import datetime
|
|
|
from typing import Any
|
|
from typing import Any
|
|
|
|
|
|
|
|
from sqlalchemy import func, select, update
|
|
from sqlalchemy import func, select, update
|
|
@@ -13,6 +15,8 @@ from supply_infra.video_discovery_gates import (
|
|
|
)
|
|
)
|
|
|
from supply_infra.db.models.video_discovery import (
|
|
from supply_infra.db.models.video_discovery import (
|
|
|
VideoDiscoveryCandidate,
|
|
VideoDiscoveryCandidate,
|
|
|
|
|
+ VideoDiscoveryEvidence,
|
|
|
|
|
+ VideoDiscoveryGateEvaluation,
|
|
|
VideoDiscoveryRun,
|
|
VideoDiscoveryRun,
|
|
|
VideoDiscoverySearch,
|
|
VideoDiscoverySearch,
|
|
|
)
|
|
)
|
|
@@ -45,6 +49,60 @@ def _merge_json_list(raw: str | None, values: list[Any]) -> str | None:
|
|
|
_PUBLISHABLE_BUCKETS = ("primary",)
|
|
_PUBLISHABLE_BUCKETS = ("primary",)
|
|
|
_BUSINESS_GOAL_PRIMARY_COUNT = 5
|
|
_BUSINESS_GOAL_PRIMARY_COUNT = 5
|
|
|
_PENDING_FINAL_REJECT_REASON = "NOT_SELECTED_AFTER_EVALUATION"
|
|
_PENDING_FINAL_REJECT_REASON = "NOT_SELECTED_AFTER_EVALUATION"
|
|
|
|
|
+_EVIDENCE_INCOMPLETE_REJECT_REASON = "EVIDENCE_INCOMPLETE"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _gate_input(entity: VideoDiscoveryCandidate) -> dict[str, Any]:
|
|
|
|
|
+ """Build the single authoritative gate input from persisted candidate facts."""
|
|
|
|
|
+ return {
|
|
|
|
|
+ "title": entity.title,
|
|
|
|
|
+ "tags_json": entity.tags_json,
|
|
|
|
|
+ "publish_at": entity.publish_at,
|
|
|
|
|
+ "duration_seconds": entity.duration_seconds,
|
|
|
|
|
+ "share_count": entity.share_count,
|
|
|
|
|
+ "like_count": entity.like_count,
|
|
|
|
|
+ "play_count": entity.play_count,
|
|
|
|
|
+ "content_50_plus_ratio": entity.content_50_plus_ratio,
|
|
|
|
|
+ "account_50_plus_ratio": entity.account_50_plus_ratio,
|
|
|
|
|
+ "relevance_score": entity.relevance_score,
|
|
|
|
|
+ "elder_score": entity.elder_score,
|
|
|
|
|
+ "share_score": entity.share_score,
|
|
|
|
|
+ "value_score": entity.value_score,
|
|
|
|
|
+ "temporal_type": entity.temporal_type,
|
|
|
|
|
+ "temporal_status": entity.temporal_status,
|
|
|
|
|
+ "temporal_evidence_json": entity.temporal_evidence_json,
|
|
|
|
|
+ "detail_fetch_status": entity.detail_fetch_status,
|
|
|
|
|
+ "content_portrait_fetch_status": entity.content_portrait_fetch_status,
|
|
|
|
|
+ "account_portrait_fetch_status": entity.account_portrait_fetch_status,
|
|
|
|
|
+ "latest_search_evidence_id": entity.latest_search_evidence_id,
|
|
|
|
|
+ "latest_detail_evidence_id": entity.latest_detail_evidence_id,
|
|
|
|
|
+ "latest_content_portrait_evidence_id": entity.latest_content_portrait_evidence_id,
|
|
|
|
|
+ "latest_account_portrait_evidence_id": entity.latest_account_portrait_evidence_id,
|
|
|
|
|
+ "evidence_version": int(entity.evidence_version or 0),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _pending_gate_codes(
|
|
|
|
|
+ gate: dict[str, Any], candidate: VideoDiscoveryCandidate
|
|
|
|
|
+) -> list[str]:
|
|
|
|
|
+ """Return missing-evidence failures that are still retryable."""
|
|
|
|
|
+ pending: list[str] = []
|
|
|
|
|
+ detail_terminal = candidate.detail_fetch_status in {"success", "unavailable"}
|
|
|
|
|
+ portrait_terminal = (
|
|
|
|
|
+ candidate.content_portrait_fetch_status in {"success", "unavailable"}
|
|
|
|
|
+ or candidate.account_portrait_fetch_status in {"success", "unavailable"}
|
|
|
|
|
+ )
|
|
|
|
|
+ for code in gate.get("failed_reason_codes") or []:
|
|
|
|
|
+ code_text = str(code)
|
|
|
|
|
+ if code_text in {
|
|
|
|
|
+ "TEMPORAL_UNKNOWN",
|
|
|
|
|
+ "DURATION_UNKNOWN",
|
|
|
|
|
+ "SHARE_COUNT_UNKNOWN",
|
|
|
|
|
+ } and not detail_terminal:
|
|
|
|
|
+ pending.append(code_text)
|
|
|
|
|
+ elif code_text == "CONTENT_PORTRAIT_MISSING" and not portrait_terminal:
|
|
|
|
|
+ pending.append(code_text)
|
|
|
|
|
+ return pending
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
@dataclass(frozen=True)
|
|
@@ -134,16 +192,167 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
self.session.flush()
|
|
self.session.flush()
|
|
|
return entity
|
|
return entity
|
|
|
|
|
|
|
|
|
|
+ def create_evidence(self, values: dict[str, Any]) -> VideoDiscoveryEvidence:
|
|
|
|
|
+ entity = VideoDiscoveryEvidence(**values)
|
|
|
|
|
+ self.session.add(entity)
|
|
|
|
|
+ self.session.flush()
|
|
|
|
|
+ return entity
|
|
|
|
|
+
|
|
|
|
|
+ def get_evidence(self, evidence_id: int) -> VideoDiscoveryEvidence | None:
|
|
|
|
|
+ return self.session.get(VideoDiscoveryEvidence, int(evidence_id))
|
|
|
|
|
+
|
|
|
|
|
+ def get_candidates_by_ids(
|
|
|
|
|
+ self, run_id: str, candidate_ids: list[int]
|
|
|
|
|
+ ) -> list[VideoDiscoveryCandidate]:
|
|
|
|
|
+ if not candidate_ids:
|
|
|
|
|
+ return []
|
|
|
|
|
+ stmt = select(VideoDiscoveryCandidate).where(
|
|
|
|
|
+ VideoDiscoveryCandidate.run_id == run_id,
|
|
|
|
|
+ VideoDiscoveryCandidate.id.in_([int(value) for value in candidate_ids]),
|
|
|
|
|
+ )
|
|
|
|
|
+ rows = list(self.session.scalars(stmt).all())
|
|
|
|
|
+ by_id = {int(item.id): item for item in rows}
|
|
|
|
|
+ missing = [int(value) for value in candidate_ids if int(value) not in by_id]
|
|
|
|
|
+ if missing:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"candidate_id 不存在或不属于 run_id={run_id}: {missing}"
|
|
|
|
|
+ )
|
|
|
|
|
+ return [by_id[int(value)] for value in candidate_ids]
|
|
|
|
|
+
|
|
|
|
|
+ def _evaluate_candidate(
|
|
|
|
|
+ self,
|
|
|
|
|
+ entity: VideoDiscoveryCandidate,
|
|
|
|
|
+ rule_snapshot: dict[str, Any],
|
|
|
|
|
+ ) -> dict[str, Any]:
|
|
|
|
|
+ input_payload = _gate_input(entity)
|
|
|
|
|
+ gate = evaluate_candidate_gate(input_payload, rule_snapshot)
|
|
|
|
|
+ pending_codes = _pending_gate_codes(gate, entity)
|
|
|
|
|
+ if pending_codes:
|
|
|
|
|
+ gate["status"] = "pending"
|
|
|
|
|
+ gate["primary_eligible"] = False
|
|
|
|
|
+ gate["pending_reason_codes"] = pending_codes
|
|
|
|
|
+
|
|
|
|
|
+ entity.content_portrait_status = gate["content_portrait_status"]
|
|
|
|
|
+ entity.account_portrait_status = gate["account_portrait_status"]
|
|
|
|
|
+ entity.portrait_conflict = int(bool(gate["portrait_conflict"]))
|
|
|
|
|
+ entity.temporal_type = gate["temporal"]["temporal_type"]
|
|
|
|
|
+ entity.temporal_status = gate["temporal"]["status"]
|
|
|
|
|
+ entity.temporal_evidence_json = json.dumps(
|
|
|
|
|
+ gate["temporal"], ensure_ascii=False, default=str
|
|
|
|
|
+ )
|
|
|
|
|
+ entity.gate_status = str(gate["status"])
|
|
|
|
|
+ entity.gate_results_json = json.dumps(gate, ensure_ascii=False, default=str)
|
|
|
|
|
+ entity.rule_version = str(gate["rule_version"])
|
|
|
|
|
+ entity.gate_evidence_version = int(entity.evidence_version or 0)
|
|
|
|
|
+ entity.gate_evaluated_at = datetime.now()
|
|
|
|
|
+
|
|
|
|
|
+ input_json = json.dumps(
|
|
|
|
|
+ input_payload, ensure_ascii=False, sort_keys=True, default=str
|
|
|
|
|
+ )
|
|
|
|
|
+ input_hash = hashlib.sha256(input_json.encode("utf-8")).hexdigest()
|
|
|
|
|
+ evaluation = VideoDiscoveryGateEvaluation(
|
|
|
|
|
+ run_id=entity.run_id,
|
|
|
|
|
+ candidate_id=int(entity.id),
|
|
|
|
|
+ rule_version=str(gate["rule_version"]),
|
|
|
|
|
+ evidence_version=int(entity.evidence_version or 0),
|
|
|
|
|
+ gate_input_hash=input_hash,
|
|
|
|
|
+ gate_input_json=input_json,
|
|
|
|
|
+ gate_result_json=json.dumps(gate, ensure_ascii=False, default=str),
|
|
|
|
|
+ gate_status=str(gate["status"]),
|
|
|
|
|
+ failed_reason_codes_json=json.dumps(
|
|
|
|
|
+ gate.get("failed_reason_codes") or [], ensure_ascii=False
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ self.session.add(evaluation)
|
|
|
|
|
+ self.session.flush()
|
|
|
|
|
+ entity.latest_gate_evaluation_id = int(evaluation.id)
|
|
|
|
|
+ return gate
|
|
|
|
|
+
|
|
|
|
|
+ def apply_evidence(
|
|
|
|
|
+ self,
|
|
|
|
|
+ run_id: str,
|
|
|
|
|
+ *,
|
|
|
|
|
+ aweme_id: str,
|
|
|
|
|
+ evidence: VideoDiscoveryEvidence,
|
|
|
|
|
+ normalized: dict[str, Any],
|
|
|
|
|
+ ) -> list[VideoDiscoveryCandidate]:
|
|
|
|
|
+ """Apply one immutable observation to every occurrence of a run video."""
|
|
|
|
|
+ run = self.get_run(run_id)
|
|
|
|
|
+ if run is None:
|
|
|
|
|
+ raise ValueError(f"run_id 不存在: {run_id}")
|
|
|
|
|
+ rule_snapshot = load_rule_snapshot(run.rule_config_json)
|
|
|
|
|
+ stmt = select(VideoDiscoveryCandidate).where(
|
|
|
|
|
+ VideoDiscoveryCandidate.run_id == run_id,
|
|
|
|
|
+ VideoDiscoveryCandidate.aweme_id == aweme_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ candidates = list(self.session.scalars(stmt).all())
|
|
|
|
|
+ if not candidates:
|
|
|
|
|
+ raise ValueError(f"run_id={run_id} 中不存在 aweme_id={aweme_id}")
|
|
|
|
|
+
|
|
|
|
|
+ pointer_name = {
|
|
|
|
|
+ "detail": "latest_detail_evidence_id",
|
|
|
|
|
+ "content_portrait": "latest_content_portrait_evidence_id",
|
|
|
|
|
+ "account_portrait": "latest_account_portrait_evidence_id",
|
|
|
|
|
+ }.get(evidence.evidence_type)
|
|
|
|
|
+ status_name = {
|
|
|
|
|
+ "detail": "detail_fetch_status",
|
|
|
|
|
+ "content_portrait": "content_portrait_fetch_status",
|
|
|
|
|
+ "account_portrait": "account_portrait_fetch_status",
|
|
|
|
|
+ }.get(evidence.evidence_type)
|
|
|
|
|
+ for candidate in candidates:
|
|
|
|
|
+ if pointer_name:
|
|
|
|
|
+ setattr(candidate, pointer_name, int(evidence.id))
|
|
|
|
|
+ if status_name:
|
|
|
|
|
+ setattr(candidate, status_name, evidence.fetch_status)
|
|
|
|
|
+ for key, value in normalized.items():
|
|
|
|
|
+ if key in {"id", "candidate_id", "run_id", "search_id", "aweme_id"}:
|
|
|
|
|
+ continue
|
|
|
|
|
+ if value is not None and hasattr(candidate, key):
|
|
|
|
|
+ setattr(candidate, key, value)
|
|
|
|
|
+ candidate.evidence_version = int(candidate.evidence_version or 0) + 1
|
|
|
|
|
+ self.session.flush()
|
|
|
|
|
+ gate = self._evaluate_candidate(candidate, rule_snapshot)
|
|
|
|
|
+ if candidate.decision_bucket == "primary" and gate["status"] != "pass":
|
|
|
|
|
+ if gate["status"] == "pending":
|
|
|
|
|
+ candidate.decision_bucket = "pending_evaluation"
|
|
|
|
|
+ candidate.reject_reason_code = None
|
|
|
|
|
+ else:
|
|
|
|
|
+ candidate.decision_bucket = "rejected"
|
|
|
|
|
+ failed_codes = gate.get("failed_reason_codes") or []
|
|
|
|
|
+ candidate.reject_reason_code = str(
|
|
|
|
|
+ failed_codes[0] if failed_codes else "P0_GATE_FAILED"
|
|
|
|
|
+ )
|
|
|
|
|
+ evidence.processing_status = "applied"
|
|
|
|
|
+ evidence.normalized_json = json.dumps(
|
|
|
|
|
+ normalized, ensure_ascii=False, default=str
|
|
|
|
|
+ )
|
|
|
|
|
+ self.session.flush()
|
|
|
|
|
+ self._refresh_run_counts(run_id)
|
|
|
|
|
+ return candidates
|
|
|
|
|
+
|
|
|
def save_search_page(
|
|
def save_search_page(
|
|
|
self,
|
|
self,
|
|
|
search_values: dict[str, Any],
|
|
search_values: dict[str, Any],
|
|
|
candidate_rows: list[dict[str, Any]],
|
|
candidate_rows: list[dict[str, Any]],
|
|
|
|
|
+ evidence_values: dict[str, Any] | None = None,
|
|
|
|
|
+ evidence_id: int | None = None,
|
|
|
) -> tuple[VideoDiscoverySearch, list[VideoDiscoveryCandidate]]:
|
|
) -> tuple[VideoDiscoverySearch, list[VideoDiscoveryCandidate]]:
|
|
|
"""新增一个搜索页,并为本页每条结果新增独立候选记录。"""
|
|
"""新增一个搜索页,并为本页每条结果新增独立候选记录。"""
|
|
|
run_id = str(search_values["run_id"])
|
|
run_id = str(search_values["run_id"])
|
|
|
|
|
+ evidence = (
|
|
|
|
|
+ self.get_evidence(evidence_id)
|
|
|
|
|
+ if evidence_id is not None
|
|
|
|
|
+ else (self.create_evidence(evidence_values) if evidence_values else None)
|
|
|
|
|
+ )
|
|
|
|
|
+ if evidence_id is not None and evidence is None:
|
|
|
|
|
+ raise ValueError(f"evidence_id 不存在: {evidence_id}")
|
|
|
search = VideoDiscoverySearch(**search_values)
|
|
search = VideoDiscoverySearch(**search_values)
|
|
|
|
|
+ if evidence is not None:
|
|
|
|
|
+ search.raw_evidence_id = int(evidence.id)
|
|
|
self.session.add(search)
|
|
self.session.add(search)
|
|
|
self.session.flush()
|
|
self.session.flush()
|
|
|
|
|
+ if evidence is not None:
|
|
|
|
|
+ evidence.search_id = int(search.id)
|
|
|
|
|
|
|
|
candidates: list[VideoDiscoveryCandidate] = []
|
|
candidates: list[VideoDiscoveryCandidate] = []
|
|
|
aweme_ids: list[str] = []
|
|
aweme_ids: list[str] = []
|
|
@@ -164,6 +373,8 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
),
|
|
),
|
|
|
source_search_ids_json=json.dumps([int(search.id)]),
|
|
source_search_ids_json=json.dumps([int(search.id)]),
|
|
|
decision_bucket="pending_evaluation",
|
|
decision_bucket="pending_evaluation",
|
|
|
|
|
+ latest_search_evidence_id=(int(evidence.id) if evidence else None),
|
|
|
|
|
+ evidence_version=(1 if evidence else 0),
|
|
|
)
|
|
)
|
|
|
for key, value in row.items():
|
|
for key, value in row.items():
|
|
|
if value is None:
|
|
if value is None:
|
|
@@ -179,6 +390,8 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
json.dumps(aweme_ids, ensure_ascii=False) if aweme_ids else None
|
|
json.dumps(aweme_ids, ensure_ascii=False) if aweme_ids else None
|
|
|
)
|
|
)
|
|
|
self.session.flush()
|
|
self.session.flush()
|
|
|
|
|
+ if evidence is not None:
|
|
|
|
|
+ evidence.processing_status = "applied"
|
|
|
self._refresh_run_counts(run_id)
|
|
self._refresh_run_counts(run_id)
|
|
|
return search, candidates
|
|
return search, candidates
|
|
|
|
|
|
|
@@ -214,6 +427,7 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
for row in rows:
|
|
for row in rows:
|
|
|
candidate_id = int(row["candidate_id"])
|
|
candidate_id = int(row["candidate_id"])
|
|
|
entity = existing[candidate_id]
|
|
entity = existing[candidate_id]
|
|
|
|
|
+ requested_bucket = str(row.get("decision_bucket") or entity.decision_bucket)
|
|
|
for key, value in row.items():
|
|
for key, value in row.items():
|
|
|
if key in {"candidate_id", "id", "run_id", "search_id", "aweme_id"}:
|
|
if key in {"candidate_id", "id", "run_id", "search_id", "aweme_id"}:
|
|
|
continue
|
|
continue
|
|
@@ -228,42 +442,25 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
setattr(entity, key, _merge_json_list(getattr(entity, key), values))
|
|
setattr(entity, key, _merge_json_list(getattr(entity, key), values))
|
|
|
elif hasattr(entity, key):
|
|
elif hasattr(entity, key):
|
|
|
setattr(entity, key, value)
|
|
setattr(entity, key, value)
|
|
|
|
|
+ entity.evidence_version = int(entity.evidence_version or 0) + 1
|
|
|
|
|
+ self.session.flush()
|
|
|
|
|
+ gate = self._evaluate_candidate(entity, rule_snapshot)
|
|
|
|
|
|
|
|
- gate = evaluate_candidate_gate(
|
|
|
|
|
- {
|
|
|
|
|
- "title": entity.title,
|
|
|
|
|
- "tags_json": entity.tags_json,
|
|
|
|
|
- "publish_at": entity.publish_at,
|
|
|
|
|
- "duration_seconds": entity.duration_seconds,
|
|
|
|
|
- "share_count": entity.share_count,
|
|
|
|
|
- "content_50_plus_ratio": entity.content_50_plus_ratio,
|
|
|
|
|
- "account_50_plus_ratio": entity.account_50_plus_ratio,
|
|
|
|
|
- "temporal_type": entity.temporal_type,
|
|
|
|
|
- "temporal_status": entity.temporal_status,
|
|
|
|
|
- "temporal_evidence_json": entity.temporal_evidence_json,
|
|
|
|
|
- },
|
|
|
|
|
- rule_snapshot,
|
|
|
|
|
- )
|
|
|
|
|
- entity.content_portrait_status = gate["content_portrait_status"]
|
|
|
|
|
- entity.account_portrait_status = gate["account_portrait_status"]
|
|
|
|
|
- entity.portrait_conflict = int(bool(gate["portrait_conflict"]))
|
|
|
|
|
- entity.temporal_type = gate["temporal"]["temporal_type"]
|
|
|
|
|
- entity.temporal_status = gate["temporal"]["status"]
|
|
|
|
|
- entity.temporal_evidence_json = json.dumps(
|
|
|
|
|
- gate["temporal"],
|
|
|
|
|
- ensure_ascii=False,
|
|
|
|
|
- default=str,
|
|
|
|
|
- )
|
|
|
|
|
- entity.gate_status = gate["status"]
|
|
|
|
|
- entity.gate_results_json = json.dumps(
|
|
|
|
|
- gate,
|
|
|
|
|
- ensure_ascii=False,
|
|
|
|
|
- default=str,
|
|
|
|
|
- )
|
|
|
|
|
- entity.rule_version = str(gate["rule_version"])
|
|
|
|
|
-
|
|
|
|
|
- if entity.decision_bucket == "primary":
|
|
|
|
|
- if not gate["primary_eligible"]:
|
|
|
|
|
|
|
+ if requested_bucket == "primary":
|
|
|
|
|
+ if gate["status"] == "pending":
|
|
|
|
|
+ entity.decision_bucket = "pending_evaluation"
|
|
|
|
|
+ entity.reject_reason_code = None
|
|
|
|
|
+ reclassified.append(
|
|
|
|
|
+ {
|
|
|
|
|
+ "candidate_id": candidate_id,
|
|
|
|
|
+ "requested_bucket": "primary",
|
|
|
|
|
+ "saved_bucket": "pending_evaluation",
|
|
|
|
|
+ "failed_reason_codes": list(
|
|
|
|
|
+ gate.get("pending_reason_codes") or []
|
|
|
|
|
+ ),
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ elif not gate["primary_eligible"]:
|
|
|
failed_codes = gate["failed_reason_codes"]
|
|
failed_codes = gate["failed_reason_codes"]
|
|
|
entity.decision_bucket = "rejected"
|
|
entity.decision_bucket = "rejected"
|
|
|
entity.reject_reason_code = str(
|
|
entity.reject_reason_code = str(
|
|
@@ -278,8 +475,10 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
else:
|
|
else:
|
|
|
|
|
+ entity.decision_bucket = "primary"
|
|
|
entity.reject_reason_code = None
|
|
entity.reject_reason_code = None
|
|
|
else:
|
|
else:
|
|
|
|
|
+ entity.decision_bucket = requested_bucket
|
|
|
failed_codes = gate["failed_reason_codes"]
|
|
failed_codes = gate["failed_reason_codes"]
|
|
|
if failed_codes:
|
|
if failed_codes:
|
|
|
entity.reject_reason_code = str(failed_codes[0])
|
|
entity.reject_reason_code = str(failed_codes[0])
|
|
@@ -328,13 +527,24 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
return "no_match"
|
|
return "no_match"
|
|
|
|
|
|
|
|
def _finalize_pending_candidates(self, run_id: str) -> None:
|
|
def _finalize_pending_candidates(self, run_id: str) -> None:
|
|
|
|
|
+ pending_buckets = ("pending_evaluation", "unreviewed")
|
|
|
self.session.execute(
|
|
self.session.execute(
|
|
|
update(VideoDiscoveryCandidate)
|
|
update(VideoDiscoveryCandidate)
|
|
|
.where(
|
|
.where(
|
|
|
VideoDiscoveryCandidate.run_id == run_id,
|
|
VideoDiscoveryCandidate.run_id == run_id,
|
|
|
- VideoDiscoveryCandidate.decision_bucket.in_(
|
|
|
|
|
- ("pending_evaluation", "unreviewed")
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ VideoDiscoveryCandidate.decision_bucket.in_(pending_buckets),
|
|
|
|
|
+ VideoDiscoveryCandidate.gate_status == "pending",
|
|
|
|
|
+ )
|
|
|
|
|
+ .values(
|
|
|
|
|
+ decision_bucket="rejected",
|
|
|
|
|
+ reject_reason_code=_EVIDENCE_INCOMPLETE_REJECT_REASON,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ self.session.execute(
|
|
|
|
|
+ update(VideoDiscoveryCandidate)
|
|
|
|
|
+ .where(
|
|
|
|
|
+ VideoDiscoveryCandidate.run_id == run_id,
|
|
|
|
|
+ VideoDiscoveryCandidate.decision_bucket.in_(pending_buckets),
|
|
|
)
|
|
)
|
|
|
.values(
|
|
.values(
|
|
|
decision_bucket="rejected",
|
|
decision_bucket="rejected",
|
|
@@ -395,6 +605,10 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
)
|
|
)
|
|
|
.where(VideoDiscoveryCandidate.decision_bucket.in_(_PUBLISHABLE_BUCKETS))
|
|
.where(VideoDiscoveryCandidate.decision_bucket.in_(_PUBLISHABLE_BUCKETS))
|
|
|
.where(VideoDiscoveryCandidate.gate_status == "pass")
|
|
.where(VideoDiscoveryCandidate.gate_status == "pass")
|
|
|
|
|
+ .where(
|
|
|
|
|
+ VideoDiscoveryCandidate.gate_evidence_version
|
|
|
|
|
+ == VideoDiscoveryCandidate.evidence_version
|
|
|
|
|
+ )
|
|
|
.where(VideoDiscoveryCandidate.aweme_id.is_not(None))
|
|
.where(VideoDiscoveryCandidate.aweme_id.is_not(None))
|
|
|
.where(func.trim(VideoDiscoveryCandidate.aweme_id) != "")
|
|
.where(func.trim(VideoDiscoveryCandidate.aweme_id) != "")
|
|
|
.order_by(
|
|
.order_by(
|
|
@@ -424,6 +638,10 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
.where(VideoDiscoveryRun.biz_dt == biz_dt)
|
|
.where(VideoDiscoveryRun.biz_dt == biz_dt)
|
|
|
.where(VideoDiscoveryCandidate.decision_bucket.in_(_PUBLISHABLE_BUCKETS))
|
|
.where(VideoDiscoveryCandidate.decision_bucket.in_(_PUBLISHABLE_BUCKETS))
|
|
|
.where(VideoDiscoveryCandidate.gate_status == "pass")
|
|
.where(VideoDiscoveryCandidate.gate_status == "pass")
|
|
|
|
|
+ .where(
|
|
|
|
|
+ VideoDiscoveryCandidate.gate_evidence_version
|
|
|
|
|
+ == VideoDiscoveryCandidate.evidence_version
|
|
|
|
|
+ )
|
|
|
.where(VideoDiscoveryCandidate.aweme_id.is_not(None))
|
|
.where(VideoDiscoveryCandidate.aweme_id.is_not(None))
|
|
|
.where(func.trim(VideoDiscoveryCandidate.aweme_id) != "")
|
|
.where(func.trim(VideoDiscoveryCandidate.aweme_id) != "")
|
|
|
)
|
|
)
|
|
@@ -479,6 +697,8 @@ class VideoDiscoveryRepository(BaseRepository[VideoDiscoveryRun]):
|
|
|
VideoDiscoveryCandidate.run_id == run_id,
|
|
VideoDiscoveryCandidate.run_id == run_id,
|
|
|
VideoDiscoveryCandidate.decision_bucket == "primary",
|
|
VideoDiscoveryCandidate.decision_bucket == "primary",
|
|
|
VideoDiscoveryCandidate.gate_status == "pass",
|
|
VideoDiscoveryCandidate.gate_status == "pass",
|
|
|
|
|
+ VideoDiscoveryCandidate.gate_evidence_version
|
|
|
|
|
+ == VideoDiscoveryCandidate.evidence_version,
|
|
|
VideoDiscoveryCandidate.aweme_id.is_not(None),
|
|
VideoDiscoveryCandidate.aweme_id.is_not(None),
|
|
|
func.trim(VideoDiscoveryCandidate.aweme_id) != "",
|
|
func.trim(VideoDiscoveryCandidate.aweme_id) != "",
|
|
|
)
|
|
)
|