feat: 拆分 wecom_shown_keys 与 movement_baseline 历史层

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 10:31:49 +08:00
parent 02e97e057f
commit f563239e0b
6 changed files with 551 additions and 5 deletions

83
daily/board_history.py Normal file
View File

@@ -0,0 +1,83 @@
"""企微展示历史:读写 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