"""账号价值分档。 列表上的「高价值待救」和巡检 / 重登共用这里的口径,避免两套规则各说各的。 分档只读账号上已经有的当前字段(官方准入三态、积分、鱼翅、宝典),不看标签。 数字越小越值钱: 1. 查过资料,兔兔白日梦和狂战萌兔都还没换 2. 查过资料、还剩一套能换,或者积分 ≥ 300(没查过但积分已经 ≥ 300 也算) 3. 有鱼翅,或宝典已开通 4. 查过资料,但上面都不沾 5. 还没查过资料 「高价值」= 第 1、2 档。巡检对这两档用更短的重检间隔,重登也先拿这两档。 """ from __future__ import annotations from datetime import UTC, datetime, timedelta from sqlalchemy import and_, case, or_ from ..models import Account from .high_value_service import ( HIGH_VALUE_FRESH_SECONDS, HIGH_VALUE_STATUS_AVAILABLE, HIGH_VALUE_STATUS_OWNED, HIGH_VALUE_STATUS_UNKNOWN, HIGH_VALUE_UNKNOWN_RETRY_SECONDS, high_value_fresh_cutoff, ) HIGH_VALUE_POINTS = 300 # 第 1、2 档都是「还值得抢救」的号。 HIGH_VALUE_TIER_MAX = 2 # 高价值账号的巡检间隔。作业上配置的间隔更短时,跟那个更短的走。 HIGH_VALUE_RECHECK_SECONDS = 6 * 3600 def high_value_fresh_expr(*, now: datetime | None = None): """高价值三态是否仍在有效期内。""" cutoff = high_value_fresh_cutoff(now=now or datetime.now(UTC)) return Account.high_value_checked_at >= cutoff def suit_status_expr(key: str): return getattr(Account, f"high_value_{key}_status") def suit_available_expr(key: str, *, now: datetime | None = None): return and_( high_value_fresh_expr(now=now), suit_status_expr(key) == HIGH_VALUE_STATUS_AVAILABLE, ) def suit_owned_expr(key: str, *, now: datetime | None = None): return and_( high_value_fresh_expr(now=now), suit_status_expr(key) == HIGH_VALUE_STATUS_OWNED, ) def high_value_unknown_expr(*, now: datetime | None = None): """已检查过,但结果过期或至少一套官方状态未知。""" return and_( Account.high_value_checked_at.isnot(None), or_( ~high_value_fresh_expr(now=now), suit_status_expr("bairimeng").is_(None), suit_status_expr("bairimeng") == HIGH_VALUE_STATUS_UNKNOWN, suit_status_expr("mengtu").is_(None), suit_status_expr("mengtu") == HIGH_VALUE_STATUS_UNKNOWN, ), ) def high_value_needs_refresh_expr( *, now: datetime | None = None, max_seconds: int = HIGH_VALUE_FRESH_SECONDS, unknown_seconds: int = HIGH_VALUE_UNKNOWN_RETRY_SECONDS, ): """高价值结论**已经过期** —— 需要再拉一次官方 ``storehome``。 和「资料是否过期」是**两件事**: - ``overview_queried_at`` 说「上次一键查询是什么时候」; - ``high_value_checked_at`` 说「上次形成高价值结论是什么时候」; 结论可以是 ``unknown``。 两者会脱钩 —— 比如一键查询里商品接口单独失败(其它项都拿到了), 资料时间被刷新了,高价值时间却停在原地。再比如 CK 失效那段时间查不动, 账号恢复后高价值结论还挂着「查不到」。 所以定时刷新必须看**后者**:高价值结论过期就该再查一次 —— 哪怕账号的 整体资料看起来还很新。 三种情况: - **过期**(查过但超过 ``max_seconds``):尽快重拉 —— 结论太旧, 而「可兑换」随时会变; - **结论被作废**(状态是 ``unknown``,比如 CK 失效期间被作废): 按 ``unknown_seconds`` 慢速重试; - **从来没形成过结论**(``checked_at IS NULL``):**不算**。那种情况要么 账号确实没查过资料(本来就该被选中),要么商品接口对这个账号稳定失败 (没配 manualID、账号没有宝典)—— 后者若每轮都重选,同一批账号会 反复撞墙,挤掉真正有新欠账的账号。它由「资料过期」那条兜着就够了。 ``max_seconds`` 由调用方给:默认 7 天,与列表上「待确认」的判定同源。 """ seconds = max(int(max_seconds or 0), 60) unknown = max(int(unknown_seconds or 0), 60) current = now or datetime.now(UTC) cutoff = current - timedelta(seconds=seconds) unknown_cutoff = current - timedelta(seconds=unknown) unknown_status = or_( suit_status_expr("bairimeng").is_(None), suit_status_expr("bairimeng") == HIGH_VALUE_STATUS_UNKNOWN, suit_status_expr("mengtu").is_(None), suit_status_expr("mengtu") == HIGH_VALUE_STATUS_UNKNOWN, ) return and_( Account.high_value_checked_at.isnot(None), or_( Account.high_value_checked_at < cutoff, and_( unknown_status, Account.high_value_checked_at < unknown_cutoff, ), ), ) def value_tier_expr(*, now: datetime | None = None): """1–5 档。没查过、也没有任何资产信号时落在第 5 档。""" available_bairimeng = suit_available_expr("bairimeng", now=now) available_mengtu = suit_available_expr("mengtu", now=now) both_available = and_(available_bairimeng, available_mengtu) one_available = or_( available_bairimeng, available_mengtu, ) return case( (both_available, 1), (or_(one_available, Account.points >= HIGH_VALUE_POINTS), 2), (or_(Account.gold_balance > 0, Account.elite_opened.is_(True)), 3), (Account.overview_queried_at.isnot(None), 4), else_=5, ) def is_high_value_expr(*, now: datetime | None = None): """第 1、2 档:巡检加密、重登插队、列表「高价值待救」都用这一条。""" return value_tier_expr(now=now) <= HIGH_VALUE_TIER_MAX def suit_sort_rank_expr(*, now: datetime | None = None): """列表「高价值套装」列的排序,和格子上看到的一致。数字越小越值钱。 0 查过、两套都还能换 1 查过、只剩一套 2 查过、两套都换过 3 还没查过(未知,不和「还能换」混在一起) 不掺积分、鱼翅。那是巡检分档的事,点这一列的人是在排套装。 """ available_bairimeng = suit_available_expr("bairimeng", now=now) available_mengtu = suit_available_expr("mengtu", now=now) owned_bairimeng = suit_owned_expr("bairimeng", now=now) owned_mengtu = suit_owned_expr("mengtu", now=now) both_available = and_(available_bairimeng, available_mengtu) one_available = or_( available_bairimeng, available_mengtu, ) both_owned = and_(owned_bairimeng, owned_mengtu) return case( (both_available, 0), (one_available, 1), (both_owned, 2), else_=3, ) def high_value_recheck_seconds(interval_seconds: int) -> int: """高价值账号多久重检一次。不会比作业配置的间隔更长。""" interval = max(int(interval_seconds or 0), 60) return min(HIGH_VALUE_RECHECK_SECONDS, interval)