"""企微展示历史:读写 data.wecom_shown_keys,与 movement_baseline 严格分离。""" from __future__ import annotations import json import logging from datetime import datetime, timedelta from pathlib import Path from typing import Any from daily.config import OUTPUT_DIR, board_dedup_days from daily.delta import RECENT_BOARD_KEYS, skill_id logger = logging.getLogger(__name__) BOARD_KEYS = RECENT_BOARD_KEYS def extract_shown_keys(board: str, items: list[dict[str, Any]]) -> list[str]: """从最终展示 items 抽取稳定 identity key。""" keys: list[str] = [] seen: set[str] = set() for item in items: if board.startswith("skills_"): key = skill_id(item) else: key = str(item.get("repo") or "") if not key or key in seen: continue seen.add(key) keys.append(key) return keys def load_recent_shown_keys( date_str: str, *, lookback_days: int | None = None, ) -> dict[str, set[str]]: """近 N 日 data.wecom_shown_keys 并集(不含当日)。缺省或读失败视为空集。""" empty = {board: set() for board in BOARD_KEYS} try: dt = datetime.strptime(date_str, "%Y-%m-%d") except ValueError: return empty days = lookback_days if lookback_days is not None else board_dedup_days() out: dict[str, set[str]] = {board: set() for board in BOARD_KEYS} for day_offset in range(1, days + 1): prev_date = (dt - timedelta(days=day_offset)).strftime("%Y-%m-%d") path = OUTPUT_DIR / f"{prev_date}.data.json" if not path.exists(): continue try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError) as exc: logger.warning("读取 wecom_shown_keys %s 失败:%s", path, exc) continue data = payload.get("data") if not isinstance(data, dict): continue shown = data.get("wecom_shown_keys") if not isinstance(shown, dict): continue for board in BOARD_KEYS: keys = shown.get(board) or [] if not isinstance(keys, list): continue out[board].update(str(k) for k in keys if k) return out def merge_wecom_shown_into_data( data: dict[str, Any], shown: dict[str, list[str]], ) -> dict[str, Any]: """写入 wecom_shown_keys,不修改 movement_baseline。""" merged = dict(data) merged["wecom_shown_keys"] = { board: list(keys) for board, keys in shown.items() } return merged