初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

@@ -0,0 +1,175 @@
"""调制因子派生:从时态快照 + 对话信号派生 [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,
)