from __future__ import annotations from typing import Any, Iterable, Optional def normalize_int_id_list(mixed_ids: Iterable[Any]) -> list[int]: """ 将混合类型的 id 列表归一化为 int 列表。 规则: - int/可转 int 的 str -> int - 其他(None/空字符串/不可解析)忽略 """ out: list[int] = [] for x in mixed_ids or []: if x is None: continue if isinstance(x, bool): # 避免 True/False 被当作 1/0 continue try: s = str(x).strip() if s == "": continue out.append(int(s)) except Exception: continue return out def merge_exclude_ids( *, already_recommended_ids: Iterable[Any], touched_or_viewed_ids: Iterable[Any], extra_exclude_content_ids: Optional[Iterable[int]] = None, ) -> list[int]: """ 合并并去重排除 id(保持首次出现顺序)。 """ merged = list(normalize_int_id_list(list(already_recommended_ids or []) + list(touched_or_viewed_ids or []))) if extra_exclude_content_ids: merged += [int(x) for x in extra_exclude_content_ids if x is not None] seen: set[int] = set() out: list[int] = [] for cid in merged: if cid in seen: continue seen.add(cid) out.append(cid) return out def normalize_or_default_locale(locale: Optional[str]) -> str: """ locale 防御式归一化: - 未传/空 -> 默认 "en" - 其他 -> 原样返回,由下游 normalize_locale 做严格校验 """ if locale is None: return "en" raw = str(locale).strip() return raw or "en" def clamp_personalization_power(power: Any, *, fallback_level: int) -> float: """ 按回退层级对 personalization_power 做防御式约束。 - L0:不改 - L1:<= 0.5 - L2/L3:= 0 """ try: p = float(power) except Exception: p = 0.0 if p != p: p = 0.0 if int(fallback_level) >= 2: return 0.0 if int(fallback_level) >= 1: return min(p, 0.5) return p