176 lines
6.3 KiB
Python
176 lines
6.3 KiB
Python
"""调制因子派生:从时态快照 + 对话信号派生 [0,1] 因子与关怀类型。
|
||
|
||
消费 ``TemporalSnapshot``(Plan 1)与 ``ModulationInput``(service 层收集的对话信号),
|
||
输出 ``ModulationFactors``(设计 §5.2)。全部纯函数,无副作用。
|
||
|
||
v1 用即时启发式(参数集中为模块常量,便于调参);节律曲线 / 窗口函数留 v2。
|
||
care_kind 触发规则见设计 §7.3。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pydantic import BaseModel
|
||
|
||
from app.modules.time_sense.snapshot import TemporalSnapshot
|
||
|
||
# ==================== v1 启发式参数(可调) ====================
|
||
|
||
# 节日喜悦度权重(设计 §5.2 festival_joy)
|
||
_FESTIVAL_JOY_WEIGHTS: dict[str, float] = {
|
||
"春节": 0.9,
|
||
"中秋节": 0.9,
|
||
"元宵节": 0.7,
|
||
"端午节": 0.7,
|
||
"七夕": 0.7,
|
||
"重阳节": 0.7,
|
||
"腊八节": 0.7,
|
||
"元旦": 0.5,
|
||
"劳动节": 0.5,
|
||
"国庆节": 0.5,
|
||
"圣诞节": 0.5,
|
||
"情人节": 0.5,
|
||
}
|
||
_FESTIVAL_JOY_BASELINE = 0.1 # 无节日
|
||
_FESTIVAL_JOY_DEFAULT = 0.5 # 未登记节日
|
||
|
||
# 季节共振(设计 §5.2 season_resonance;v2 用季节调制函数)
|
||
_SEASON_RESONANCE: dict[str, float] = {"春": 0.5, "夏": 0.6, "秋": 0.7, "冬": 0.6}
|
||
|
||
# 时段共振(设计 §5.2 same_period_resonance;v2 用窗口函数)
|
||
_DAY_PERIOD_RESONANCE: dict[str, float] = {
|
||
"凌晨": 0.8,
|
||
"深夜": 0.7,
|
||
"清晨": 0.4,
|
||
"夜晚": 0.4,
|
||
"傍晚": 0.3,
|
||
"上午": 0.2,
|
||
"下午": 0.2,
|
||
"中午": 0.2,
|
||
}
|
||
|
||
# 深夜脆弱度(设计 §5.2 late_night_fragility)
|
||
_FRAGILITY_DEEP_NIGHT_BASE = 0.7 # 凌晨基线
|
||
_FRAGILITY_LATE_NIGHT_BASE = 0.5 # 深夜基线
|
||
_FRAGILITY_DAYTIME = 0.1
|
||
_FRAGILITY_PER_TURN_DEEP = 0.05 # 凌晨每轮增量
|
||
_FRAGILITY_PER_TURN_LATE = 0.03 # 深夜每轮增量
|
||
|
||
# 周末放松度
|
||
_RELAX_WEEKEND = 0.8
|
||
_RELAX_WEEKEND_WITH_FESTIVAL = 0.9
|
||
_RELAX_WORKDAY = 0.2
|
||
|
||
# 关怀
|
||
_CARE_SUGGEST_HIT = 0.7 # care_kind 命中时
|
||
_CARE_SUGGEST_MISS_FACTOR = 0.5 # 未命中时按脆弱度折半
|
||
|
||
# 时差关怀阈值(分钟,3 小时)
|
||
_JETLAG_DIFF_MINUTES = 180
|
||
# 深夜持续聊天阈值(轮数)
|
||
_LATE_NIGHT_TURNS = 3
|
||
|
||
|
||
class ModulationInput(BaseModel):
|
||
"""compute_modulation 的对话信号输入。
|
||
|
||
信号由 Plan 3 service 层收集(轮数统计、内容分类等);本模块只做纯函数判定。
|
||
关怀阈值(``jetlag_threshold_minutes`` / ``late_night_min_turns``)默认 None,
|
||
由 service 层从 ``settings`` 注入(设计 §7.1/§7.3"阈值可调");None 时回退模块常量。
|
||
"""
|
||
|
||
conversation_turns_today: int = 0
|
||
is_first_message_today: bool = False
|
||
content_work_related: bool = False
|
||
content_lonely: bool = False
|
||
user_birthday_today: bool = False
|
||
# 关怀触发阈值(None → 回退模块常量;service 层从 settings 注入,设计 §7.3)
|
||
jetlag_threshold_minutes: int | None = None
|
||
late_night_min_turns: int | None = None
|
||
|
||
|
||
class ModulationFactors(BaseModel):
|
||
"""辰正输出的调制因子(设计 §5.2)。"""
|
||
|
||
festival_joy: float
|
||
late_night_fragility: float
|
||
reunion_intensity: float
|
||
weekend_relax: float
|
||
season_resonance: float
|
||
same_period_resonance: float
|
||
care_suggest: float
|
||
care_kind: str | None = None
|
||
|
||
|
||
def _festival_joy(snapshot: TemporalSnapshot) -> float:
|
||
if not snapshot.festivals:
|
||
return _FESTIVAL_JOY_BASELINE
|
||
return max(_FESTIVAL_JOY_WEIGHTS.get(f.name, _FESTIVAL_JOY_DEFAULT) for f in snapshot.festivals)
|
||
|
||
|
||
def _late_night_fragility(snapshot: TemporalSnapshot, turns: int) -> float:
|
||
period = snapshot.day_period
|
||
if period == "凌晨":
|
||
return min(1.0, _FRAGILITY_DEEP_NIGHT_BASE + turns * _FRAGILITY_PER_TURN_DEEP)
|
||
if period == "深夜":
|
||
return min(1.0, _FRAGILITY_LATE_NIGHT_BASE + turns * _FRAGILITY_PER_TURN_LATE)
|
||
return _FRAGILITY_DAYTIME
|
||
|
||
|
||
def _weekend_relax(snapshot: TemporalSnapshot) -> float:
|
||
if not snapshot.is_weekend:
|
||
return _RELAX_WORKDAY
|
||
if snapshot.festivals:
|
||
return _RELAX_WEEKEND_WITH_FESTIVAL
|
||
return _RELAX_WEEKEND
|
||
|
||
|
||
def _care_kind(snapshot: TemporalSnapshot, signals: ModulationInput) -> str | None:
|
||
"""设计 §7.3 关怀触发判定(按优先级)。
|
||
|
||
阈值读 ``signals``(None 回退模块常量),由 service 层从 settings 注入(§7.3 可调)。
|
||
"""
|
||
deep = snapshot.day_period in {"凌晨", "深夜"}
|
||
jetlag_threshold = signals.jetlag_threshold_minutes or _JETLAG_DIFF_MINUTES
|
||
late_night_turns = signals.late_night_min_turns or _LATE_NIGHT_TURNS
|
||
# 1. 时差关怀:深夜/凌晨 + 家时差显著
|
||
if deep and abs(snapshot.home_tz_diff_minutes) >= jetlag_threshold:
|
||
return "jetlag"
|
||
# 2. 用户生日
|
||
if signals.user_birthday_today:
|
||
return "birthday"
|
||
# 3. 清晨异常:清晨时段 + 本地 4-6 点 + 今日首条
|
||
if signals.is_first_message_today and snapshot.day_period == "清晨" and snapshot.local_hour < 6:
|
||
return "early_morning"
|
||
# 4. 深夜工作话题
|
||
if deep and signals.content_work_related:
|
||
return "overwork"
|
||
# 5. 节日孤独
|
||
if bool(snapshot.festivals) and signals.content_lonely:
|
||
return "festival_lonely"
|
||
# 6. 深夜持续聊天:深夜/凌晨 + 连续 ≥ N 轮
|
||
if deep and signals.conversation_turns_today >= late_night_turns:
|
||
return "late_night"
|
||
return None
|
||
|
||
|
||
def _care_suggest(fragility: float, care_kind_value: str | None) -> float:
|
||
if care_kind_value is not None:
|
||
return _CARE_SUGGEST_HIT
|
||
return round(fragility * _CARE_SUGGEST_MISS_FACTOR, 3)
|
||
|
||
|
||
def compute_modulation(snapshot: TemporalSnapshot, signals: ModulationInput) -> ModulationFactors:
|
||
"""从时态快照 + 对话信号派生调制因子。纯函数。"""
|
||
fragility = _late_night_fragility(snapshot, signals.conversation_turns_today)
|
||
care_kind_value = _care_kind(snapshot, signals)
|
||
return ModulationFactors(
|
||
festival_joy=round(_festival_joy(snapshot), 3),
|
||
late_night_fragility=round(fragility, 3),
|
||
reunion_intensity=round(snapshot.offline.reunion_intensity, 3),
|
||
weekend_relax=_weekend_relax(snapshot),
|
||
season_resonance=_SEASON_RESONANCE.get(snapshot.season, 0.5),
|
||
same_period_resonance=_DAY_PERIOD_RESONANCE.get(snapshot.day_period, 0.3),
|
||
care_suggest=_care_suggest(fragility, care_kind_value),
|
||
care_kind=care_kind_value,
|
||
)
|