"""叙事轴硬互斥:代码选定轴,注入 Agent Step1 并强制覆写。""" from __future__ import annotations import json import logging import random from datetime import datetime, timedelta from typing import Any from daily.config import OUTPUT_DIR, narrative_axis_days logger = logging.getLogger(__name__) NARRATIVE_AXES: tuple[str, ...] = ( "政策监管", "模型发布", "工具链/Agent", "芯片算力", "开源生态", "应用落地", "安全/诉讼", ) def pick_narrative_axis( used: set[str], *, rng: random.Random | None = None, ) -> str: """从固定轴枚举中排除已用轴后随机选取;全用尽则回退全表。""" available = [a for a in NARRATIVE_AXES if a not in used] pool = available or list(NARRATIVE_AXES) picker = rng or random.Random() return picker.choice(pool) def load_recent_axes(date_str: str, days: int | None = None) -> list[str]: """近 N 日 data.narrative_axis(不含当日,按时间从近到远)。""" lookback = days if days is not None else narrative_axis_days() try: dt = datetime.strptime(date_str, "%Y-%m-%d") except ValueError: return [] axes: list[str] = [] for day_offset in range(1, lookback + 1): prev = (dt - timedelta(days=day_offset)).strftime("%Y-%m-%d") path = OUTPUT_DIR / f"{prev}.data.json" if not path.exists(): continue try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError) as exc: logger.warning("读取 narrative_axis %s 失败:%s", path, exc) continue data = payload.get("data") if isinstance(payload, dict) else None if not isinstance(data, dict): continue axis = str(data.get("narrative_axis") or "").strip() if axis: axes.append(axis) return axes def load_recent_theme_summaries(date_str: str, days: int) -> list[str]: """近 N 日 theme/opening 摘要,供 Step1 软禁参考。""" try: dt = datetime.strptime(date_str, "%Y-%m-%d") except ValueError: return [] summaries: list[str] = [] for day_offset in range(1, days + 1): prev = (dt - timedelta(days=day_offset)).strftime("%Y-%m-%d") path = OUTPUT_DIR / f"{prev}.data.json" if not path.exists(): continue try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError): continue data = payload.get("data") if isinstance(payload, dict) else None if not isinstance(data, dict): continue theme = str(data.get("theme") or data.get("editorial_theme") or "").strip() opening = "" trends = data.get("trends") if isinstance(data.get("trends"), dict) else {} if isinstance(trends, dict): opening = str(trends.get("opening") or "").strip() if not theme: themes = trends.get("themes") or [] if themes and isinstance(themes[0], dict): theme = str(themes[0].get("title") or "").strip() bit = " · ".join(x for x in (prev, theme, opening[:40]) if x) if bit: summaries.append(bit) return summaries def enforce_narrative_axis(trends: dict[str, Any], axis: str) -> dict[str, Any]: """强制 trends['narrative_axis'] = axis。""" out = dict(trends) out["narrative_axis"] = axis return out