"""感慨触发判定(辰正设计 §7.2)。 纯函数:消费 ``TemporalSnapshot`` + 对话/记忆信号,返回 ``sentiment_trigger`` 或 None。 触发类型对齐契约字典 §11.4.7。v1 实时判定(流 A/C 入口);``season_resonance`` 留 v2。 """ from __future__ import annotations from pydantic import BaseModel from app.modules.time_sense.snapshot import TemporalSnapshot class SentimentInput(BaseModel): """detect_sentiment 的对话/记忆信号输入(service 层收集)。""" duration_days: int # 认识天数 next_milestone_days: int | None = None # 距下一周年/里程碑天数;None=无下一里程碑 has_active_milestone: bool = False # 今日是否命中里程碑 is_reunion: bool = False # 是否处于重逢档 has_reunion_memory: bool = False # 是否存在临别前最后一批记忆线索 strong_solar_terms: set[str] = set() # 强节气名集合(来自 TimeConfig) prewarm_days: int = 7 # 周年预热窗口(来自 TimeConfig) duration_nodes: set[int] = set() # 有感天数节点(来自 TimeConfig) def detect_sentiment(snapshot: TemporalSnapshot, signals: SentimentInput) -> str | None: """按设计 §7.2 判定感慨触发,返回 ``sentiment_trigger`` 或 None。 优先级(v1):duration_node > anniversary_prewarm > reunion_memory > late_night_overlay > solar_term。season_resonance 留 v2。 """ # 1. duration_node:认识天数命中节点 if signals.duration_days in signals.duration_nodes: return "duration_node" # 2. anniversary_prewarm:距周年 ≤ prewarm_days if ( signals.next_milestone_days is not None and 0 < signals.next_milestone_days <= signals.prewarm_days ): return "anniversary_prewarm" # 3. reunion_memory:久别重逢 + 存在临别记忆 if signals.is_reunion and signals.has_reunion_memory: return "reunion_memory" # 4. late_night_overlay:凌晨 + 里程碑/重逢叠加 if snapshot.day_period == "凌晨" and (signals.has_active_milestone or signals.is_reunion): return "late_night_overlay" # 5. solar_term:今日处于强节气 term_name = snapshot.solar_term.current_name if snapshot.solar_term else None if term_name and term_name in signals.strong_solar_terms: return "solar_term" return None