62 lines
1.7 KiB
Python
62 lines
1.7 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Optional
|
||
|
||
from app.features.personalized_reco.observability.types import EmptyReason, MissingFields
|
||
|
||
|
||
def compute_missing_fields(user_profile: object) -> MissingFields:
|
||
"""
|
||
判定用户画像缺失字段(对齐算法规则 V1.2 口径)。
|
||
|
||
规则:
|
||
- need:user_profile.need 为空对象 {} 或不存在
|
||
- context:user_profile.context 为空对象 {} 或不存在
|
||
- emotion:user_profile.emotion_score 为 None 或不存在
|
||
"""
|
||
|
||
need = getattr(user_profile, "need", None)
|
||
context = getattr(user_profile, "context", None)
|
||
emotion_score = getattr(user_profile, "emotion_score", None)
|
||
|
||
need_missing = not bool(need)
|
||
context_missing = not bool(context)
|
||
emotion_missing = emotion_score is None
|
||
|
||
return MissingFields(need=need_missing, context=context_missing, emotion=emotion_missing)
|
||
|
||
|
||
def compute_empty_reason(
|
||
*,
|
||
served_k: int,
|
||
candidate_pool_size_raw: int,
|
||
candidate_pool_size_after_hard_filter: int,
|
||
candidate_pool_size_after_freqcap: int,
|
||
) -> Optional[EmptyReason]:
|
||
"""
|
||
判定 empty_reason(served_k=0 必填)。
|
||
|
||
规则(对齐 plan):
|
||
- served_k>0 -> None
|
||
- raw==0 -> pool_empty
|
||
- raw>0 且 after_hard_filter==0 -> hard_filter_all
|
||
- after_freqcap==0 -> freqcap_all
|
||
- 其他 -> unknown
|
||
"""
|
||
|
||
if int(served_k) > 0:
|
||
return None
|
||
|
||
raw = int(candidate_pool_size_raw)
|
||
after_hard = int(candidate_pool_size_after_hard_filter)
|
||
after_freqcap = int(candidate_pool_size_after_freqcap)
|
||
|
||
if raw == 0:
|
||
return "pool_empty"
|
||
if raw > 0 and after_hard == 0:
|
||
return "hard_filter_all"
|
||
if after_freqcap == 0:
|
||
return "freqcap_all"
|
||
return "unknown"
|
||
|