Files
MuMuAINovel/backend/app/services/character_state_update_service.py

830 lines
36 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""角色状态更新服务 - 根据章节分析结果自动更新角色心理状态、关系和组织成员"""
from typing import Dict, Any, List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, or_, and_
from app.models.character import Character
from app.models.relationship import CharacterRelationship, Organization, OrganizationMember
from app.logger import get_logger
import uuid
logger = get_logger(__name__)
# 亲密度调整关键词映射
INTIMACY_ADJUSTMENTS = {
# 正向变化
"改善": +10, "加深": +15, "信任": +10, "亲近": +15,
"友好": +10, "认可": +10, "合作": +5, "和解": +20,
"喜欢": +15, "": +20, "尊敬": +10, "感激": +10,
"好转": +10, "增进": +10, "亲密": +15, "忠诚": +10,
# 负向变化
"恶化": -10, "疏远": -15, "背叛": -30, "敌对": -25,
"矛盾": -10, "冲突": -15, "怀疑": -10, "不信任": -15,
"厌恶": -20, "仇恨": -25, "决裂": -30, "猜忌": -10,
"紧张": -5, "破裂": -25, "反目": -25, "嫉妒": -10,
# 特殊变化
"初识": 0, "相遇": 0, "结盟": +10, "分离": -5,
}
class CharacterStateUpdateService:
"""角色状态更新服务 - 根据章节分析结果自动更新角色心理状态和关系"""
@staticmethod
async def update_from_analysis(
db: AsyncSession,
project_id: str,
character_states: List[Dict[str, Any]],
chapter_id: str,
chapter_number: int
) -> Dict[str, Any]:
"""
根据章节分析结果更新角色状态和关系
Args:
db: 数据库会话
project_id: 项目ID
character_states: 角色状态变化列表(来自PlotAnalysis
chapter_id: 章节ID
chapter_number: 章节编号
Returns:
更新结果字典
"""
if not character_states:
logger.info("📋 角色状态列表为空,跳过状态和关系更新")
return {
"state_updated_count": 0,
"relationship_created_count": 0,
"relationship_updated_count": 0,
"org_updated_count": 0,
"changes": []
}
result = {
"state_updated_count": 0,
"relationship_created_count": 0,
"relationship_updated_count": 0,
"org_updated_count": 0,
"changes": []
}
logger.info(f"🔍 开始分析第{chapter_number}章的角色状态、关系和组织变化...")
# 预加载项目所有角色(含组织,按名称索引,减少重复查询)
all_characters_result = await db.execute(
select(Character).where(Character.project_id == project_id)
)
all_characters = all_characters_result.scalars().all()
# 非组织角色按名称索引
characters_by_name: Dict[str, Character] = {
c.name: c for c in all_characters if not c.is_organization
}
# 预加载组织信息(按组织角色名称索引)
orgs_result = await db.execute(
select(Organization).where(Organization.project_id == project_id)
)
all_orgs = orgs_result.scalars().all()
# 构建 character_id -> name 的反向映射
char_id_to_name: Dict[str, str] = {c.id: c.name for c in all_characters}
# 组织名称 -> Organization 映射
org_by_name: Dict[str, Organization] = {}
for org in all_orgs:
org_char_name = char_id_to_name.get(org.character_id)
if org_char_name:
org_by_name[org_char_name] = org
for char_state in character_states:
char_name = char_state.get('character_name')
if not char_name:
continue
character = characters_by_name.get(char_name)
if not character:
logger.warning(f" ⚠️ 角色不存在: {char_name},跳过状态更新")
continue
# 0. 检查角色存活状态变化
survival_status = char_state.get('survival_status')
if survival_status and survival_status in ('deceased', 'missing', 'retired'):
await CharacterStateUpdateService._update_survival_status(
db=db,
project_id=project_id,
character=character,
new_status=survival_status,
chapter_number=chapter_number,
key_event=char_state.get('key_event', ''),
changes=result["changes"]
)
result["state_updated_count"] += 1
# 死亡/失踪后不再更新心理状态等,直接跳到下一个角色
continue
# 1. 更新心理状态
state_updated = await CharacterStateUpdateService._update_psychological_state(
character=character,
char_state=char_state,
chapter_number=chapter_number,
changes=result["changes"]
)
if state_updated:
result["state_updated_count"] += 1
# 2. 更新关系
relationship_changes = char_state.get('relationship_changes', {})
if relationship_changes and isinstance(relationship_changes, dict):
created, updated = await CharacterStateUpdateService._update_relationships(
db=db,
project_id=project_id,
character=character,
relationship_changes=relationship_changes,
chapter_number=chapter_number,
chapter_id=chapter_id,
characters_by_name=characters_by_name,
changes=result["changes"]
)
result["relationship_created_count"] += created
result["relationship_updated_count"] += updated
# 3. 更新组织成员关系
organization_changes = char_state.get('organization_changes', [])
if organization_changes and isinstance(organization_changes, list):
org_updated = await CharacterStateUpdateService._update_organization_memberships(
db=db,
project_id=project_id,
character=character,
organization_changes=organization_changes,
chapter_number=chapter_number,
org_by_name=org_by_name,
changes=result["changes"]
)
result["org_updated_count"] += org_updated
# 提交所有更改
total_changes = (
result["state_updated_count"] +
result["relationship_created_count"] +
result["relationship_updated_count"] +
result["org_updated_count"]
)
if total_changes > 0:
await db.commit()
logger.info(
f"✅ 角色状态更新完成: "
f"心理状态{result['state_updated_count']}个, "
f"新建关系{result['relationship_created_count']}个, "
f"更新关系{result['relationship_updated_count']}个, "
f"组织变动{result['org_updated_count']}"
)
else:
logger.info("📋 本章没有角色状态或关系变化")
return result
@staticmethod
async def _update_survival_status(
db: AsyncSession,
project_id: str,
character: Character,
new_status: str,
chapter_number: int,
key_event: str,
changes: List[str]
) -> None:
"""
更新角色存活状态及级联影响
死亡/失踪时:
- 更新 Character.status 和 status_changed_chapter
- 更新所有活跃关系状态为 past
- 更新所有组织成员身份为 deceased/retired
"""
STATUS_DESC = {
'deceased': '死亡',
'missing': '失踪',
'retired': '退场'
}
status_desc = STATUS_DESC.get(new_status, new_status)
# 防止低章节覆盖
if (character.status_changed_chapter is not None
and chapter_number < character.status_changed_chapter):
logger.info(f" ⏭️ {character.name} 状态已在第{character.status_changed_chapter}章变更,跳过")
return
old_status = character.status or 'active'
character.status = new_status
character.status_changed_chapter = chapter_number
character.current_state = f"{status_desc}(第{chapter_number}章)"
character.state_updated_chapter = chapter_number
event_desc = f"{key_event[:50]}" if key_event else ""
changes.append(f"💀 {character.name} {status_desc}{event_desc}")
logger.info(f" 💀 {character.name} 状态: {old_status}{new_status}")
# 级联更新:所有活跃关系变为 past
rels_result = await db.execute(
select(CharacterRelationship).where(
and_(
CharacterRelationship.project_id == project_id,
CharacterRelationship.status == 'active',
or_(
CharacterRelationship.character_from_id == character.id,
CharacterRelationship.character_to_id == character.id
)
)
)
)
active_rels = rels_result.scalars().all()
for rel in active_rels:
rel.status = 'past'
rel.ended_at = f"{chapter_number}"
if active_rels:
logger.info(f" 📋 {character.name} {status_desc}{len(active_rels)}条关系标记为past")
# 级联更新:所有组织成员身份
member_status = 'deceased' if new_status == 'deceased' else 'retired'
members_result = await db.execute(
select(OrganizationMember).where(
and_(
OrganizationMember.character_id == character.id,
OrganizationMember.status == 'active'
)
)
)
active_members = members_result.scalars().all()
for member in active_members:
member.status = member_status
member.left_at = f"{chapter_number}"
member.notes = (
f"{member.notes or ''}\n[第{chapter_number}章] 角色{status_desc}"
).strip()
if active_members:
logger.info(f" 📋 {character.name} {status_desc}{len(active_members)}个组织身份标记为{member_status}")
@staticmethod
async def _update_psychological_state(
character: Character,
char_state: Dict[str, Any],
chapter_number: int,
changes: List[str]
) -> bool:
"""
更新角色心理状态
Args:
character: 角色对象
char_state: 角色状态数据
chapter_number: 章节号
changes: 变更日志列表
Returns:
是否有实际更新
"""
state_after = char_state.get('state_after')
if not state_after:
return False
# 章节号校验:防止低章节分析覆盖高章节状态
if (character.state_updated_chapter is not None
and chapter_number < character.state_updated_chapter):
logger.info(
f" ⏭️ {character.name} 的心理状态已被第{character.state_updated_chapter}章更新,"
f"跳过第{chapter_number}章的更新"
)
return False
old_state = character.current_state
character.current_state = state_after
character.state_updated_chapter = chapter_number
state_before = char_state.get('state_before', '未知')
psychological_change = char_state.get('psychological_change', '')
change_desc = f"👤 {character.name} 心理状态: {state_before}{state_after}"
if psychological_change:
change_desc += f" ({psychological_change[:50]})"
changes.append(change_desc)
logger.info(f"{character.name} 心理状态更新: {state_before}{state_after}")
return True
@staticmethod
async def _update_relationships(
db: AsyncSession,
project_id: str,
character: Character,
relationship_changes: Dict[str, Any],
chapter_number: int,
chapter_id: str,
characters_by_name: Dict[str, Character],
changes: List[str]
) -> tuple[int, int]:
"""
更新角色关系
关系名称直接使用AI分析返回的变化描述,不强制映射到预定义类型。
relationship_type_id 仅在能明确匹配时作为辅助设置。
Args:
db: 数据库会话
project_id: 项目ID
character: 角色A
relationship_changes: 关系变化字典 {"角色名": "变化描述"{"change": ..., ...}}
chapter_number: 章节号
chapter_id: 章节ID
characters_by_name: 角色名到角色对象的映射
changes: 变更日志列表
Returns:
(新建数量, 更新数量)
"""
created_count = 0
updated_count = 0
for target_name, change_info in relationship_changes.items():
try:
# 解析变化信息(支持两种格式)
if isinstance(change_info, str):
change_desc = change_info
elif isinstance(change_info, dict):
change_desc = change_info.get('change', str(change_info))
else:
change_desc = str(change_info)
if not change_desc:
continue
# 查找目标角色
target_character = characters_by_name.get(target_name)
if not target_character:
logger.warning(f" ⚠️ 关系目标角色不存在: {target_name},跳过")
continue
# 避免自身关系
if character.id == target_character.id:
continue
# 查询是否已存在关系(A→B 或 B→A)
existing_rel_result = await db.execute(
select(CharacterRelationship).where(
and_(
CharacterRelationship.project_id == project_id,
or_(
and_(
CharacterRelationship.character_from_id == character.id,
CharacterRelationship.character_to_id == target_character.id
),
and_(
CharacterRelationship.character_from_id == target_character.id,
CharacterRelationship.character_to_id == character.id
)
)
)
)
)
existing_rel = existing_rel_result.scalar_one_or_none()
# 计算亲密度调整
intimacy_delta = CharacterStateUpdateService._calculate_intimacy_delta(change_desc)
if existing_rel:
# 更新已有关系
# 更新关系名称为最新的变化描述(以AI分析结果为准)
existing_rel.relationship_name = change_desc
# 追加变更记录到描述
chapter_note = f"[第{chapter_number}章] {change_desc}"
if existing_rel.description:
existing_rel.description = f"{existing_rel.description}\n{chapter_note}"
else:
existing_rel.description = chapter_note
# 调整亲密度
if intimacy_delta != 0:
old_intimacy = existing_rel.intimacy_level or 0
new_intimacy = max(-100, min(100, old_intimacy + intimacy_delta))
existing_rel.intimacy_level = new_intimacy
logger.info(
f" 📊 {character.name}{target_name} 亲密度: "
f"{old_intimacy}{new_intimacy} ({'+' if intimacy_delta > 0 else ''}{intimacy_delta})"
)
updated_count += 1
changes.append(
f"🔄 {character.name}{target_name} 关系更新: {change_desc}"
)
logger.info(f" ✅ 更新关系: {character.name}{target_name} - {change_desc}")
else:
# 创建新关系 — 关系名称直接使用AI的变化描述
# 设定初始亲密度
initial_intimacy = max(-100, min(100, 50 + intimacy_delta))
new_relationship = CharacterRelationship(
id=str(uuid.uuid4()),
project_id=project_id,
character_from_id=character.id,
character_to_id=target_character.id,
relationship_type_id=None, # 不强制关联预定义类型
relationship_name=change_desc, # 直接使用AI分析返回的描述
intimacy_level=initial_intimacy,
status="active",
description=f"[第{chapter_number}章] {change_desc}",
source="analysis"
)
db.add(new_relationship)
created_count += 1
changes.append(
f"{character.name}{target_name} 新关系: {change_desc}"
)
logger.info(
f" ✅ 创建关系: {character.name}{target_name} "
f"({change_desc}, 亲密度:{initial_intimacy})"
)
except Exception as item_error:
logger.error(
f" ❌ 更新 {character.name}{target_name} 关系失败: {str(item_error)}"
)
return created_count, updated_count
@staticmethod
async def _update_organization_memberships(
db: AsyncSession,
project_id: str,
character: Character,
organization_changes: List[Dict[str, Any]],
chapter_number: int,
org_by_name: Dict[str, Organization],
changes: List[str]
) -> int:
"""
更新角色的组织成员关系
Args:
db: 数据库会话
project_id: 项目ID
character: 角色对象
organization_changes: 组织变动列表
chapter_number: 章节号
org_by_name: 组织名称到Organization对象的映射
changes: 变更日志列表
Returns:
更新数量
"""
updated_count = 0
# 忠诚度变化关键词映射
LOYALTY_ADJUSTMENTS = {
"提升": +10, "增强": +10, "坚定": +15, "忠心": +15,
"动摇": -15, "怀疑": -10, "不满": -10, "降低": -10,
"背叛": -50, "叛变": -50, "反感": -20, "失望": -15,
}
for org_change in organization_changes:
try:
org_name = org_change.get('organization_name')
change_type = org_change.get('change_type', '')
new_position = org_change.get('new_position')
loyalty_change_desc = org_change.get('loyalty_change', '')
description = org_change.get('description', '')
if not org_name:
continue
# 查找组织
organization = org_by_name.get(org_name)
if not organization:
logger.warning(f" ⚠️ 组织不存在: {org_name},跳过组织变动更新")
continue
# 查找已有成员关系
existing_member_result = await db.execute(
select(OrganizationMember).where(
and_(
OrganizationMember.organization_id == organization.id,
OrganizationMember.character_id == character.id
)
)
)
existing_member = existing_member_result.scalar_one_or_none()
# 计算忠诚度变化
loyalty_delta = 0
if loyalty_change_desc:
for keyword, adjustment in LOYALTY_ADJUSTMENTS.items():
if keyword in loyalty_change_desc:
loyalty_delta += adjustment
loyalty_delta = max(-50, min(50, loyalty_delta))
if change_type == 'joined':
# 加入组织
if existing_member:
# 已存在,可能是重新加入
if existing_member.status != 'active':
existing_member.status = 'active'
existing_member.left_at = None
if new_position:
existing_member.position = new_position
existing_member.notes = (
f"{existing_member.notes or ''}\n[第{chapter_number}章] 重新加入: {description}"
).strip()
updated_count += 1
changes.append(f"🏛️ {character.name} 重新加入 {org_name}")
logger.info(f"{character.name} 重新加入 {org_name}")
else:
# 创建新成员关系
new_member = OrganizationMember(
id=str(uuid.uuid4()),
organization_id=organization.id,
character_id=character.id,
position=new_position or '成员',
rank=0,
loyalty=max(0, min(100, 50 + loyalty_delta)),
status='active',
joined_at=f"{chapter_number}",
source='analysis',
notes=f"[第{chapter_number}章] {description}" if description else None
)
db.add(new_member)
organization.member_count = (organization.member_count or 0) + 1
updated_count += 1
changes.append(f"🏛️ {character.name} 加入 {org_name}({new_position or '成员'})")
logger.info(f"{character.name} 加入 {org_name}{new_position or '成员'}")
elif change_type in ('left', 'expelled', 'betrayed'):
# 离开/被开除/叛变
if existing_member and existing_member.status == 'active':
status_map = {
'left': 'retired',
'expelled': 'expelled',
'betrayed': 'expelled'
}
existing_member.status = status_map.get(change_type, 'retired')
existing_member.left_at = f"{chapter_number}"
if loyalty_delta != 0:
existing_member.loyalty = max(0, min(100, (existing_member.loyalty or 50) + loyalty_delta))
existing_member.notes = (
f"{existing_member.notes or ''}\n[第{chapter_number}章] {change_type}: {description}"
).strip()
updated_count += 1
type_desc = {'left': '离开', 'expelled': '被开除', 'betrayed': '叛变'}
changes.append(f"🏛️ {character.name} {type_desc.get(change_type, change_type)} {org_name}")
logger.info(f"{character.name} {type_desc.get(change_type, change_type)} {org_name}")
elif change_type == 'promoted':
# 晋升
if existing_member:
old_position = existing_member.position
if new_position:
existing_member.position = new_position
existing_member.rank = (existing_member.rank or 0) + 1
if loyalty_delta != 0:
existing_member.loyalty = max(0, min(100, (existing_member.loyalty or 50) + loyalty_delta))
elif loyalty_delta == 0:
# 晋升默认提升忠诚度
existing_member.loyalty = max(0, min(100, (existing_member.loyalty or 50) + 5))
existing_member.notes = (
f"{existing_member.notes or ''}\n[第{chapter_number}章] 晋升: {old_position}{new_position or '更高职位'}: {description}"
).strip()
updated_count += 1
changes.append(f"🏛️ {character.name}{org_name} 晋升: {old_position}{new_position or '更高职位'}")
logger.info(f"{character.name}{org_name} 晋升为 {new_position or '更高职位'}")
else:
logger.warning(f" ⚠️ {character.name} 不是 {org_name} 的成员,无法晋升")
elif change_type == 'demoted':
# 降级
if existing_member:
old_position = existing_member.position
if new_position:
existing_member.position = new_position
existing_member.rank = max(0, (existing_member.rank or 0) - 1)
if loyalty_delta != 0:
existing_member.loyalty = max(0, min(100, (existing_member.loyalty or 50) + loyalty_delta))
elif loyalty_delta == 0:
# 降级默认降低忠诚度
existing_member.loyalty = max(0, min(100, (existing_member.loyalty or 50) - 5))
existing_member.notes = (
f"{existing_member.notes or ''}\n[第{chapter_number}章] 降级: {old_position}{new_position or '更低职位'}: {description}"
).strip()
updated_count += 1
changes.append(f"🏛️ {character.name}{org_name} 降级: {old_position}{new_position or '更低职位'}")
logger.info(f"{character.name}{org_name} 降级为 {new_position or '更低职位'}")
else:
logger.warning(f" ⚠️ {character.name} 不是 {org_name} 的成员,无法降级")
else:
# 其他类型的变化(如忠诚度变化等)
if existing_member and loyalty_delta != 0:
old_loyalty = existing_member.loyalty or 50
existing_member.loyalty = max(0, min(100, old_loyalty + loyalty_delta))
existing_member.notes = (
f"{existing_member.notes or ''}\n[第{chapter_number}章] {change_type}: {description}"
).strip()
updated_count += 1
changes.append(
f"🏛️ {character.name}{org_name} 忠诚度变化: "
f"{old_loyalty}{existing_member.loyalty}"
)
logger.info(
f"{character.name}{org_name} 忠诚度: "
f"{old_loyalty}{existing_member.loyalty}"
)
except Exception as item_error:
logger.error(
f" ❌ 更新 {character.name} 的组织 {org_change.get('organization_name', '未知')} 变动失败: {str(item_error)}"
)
return updated_count
@staticmethod
async def update_organization_states(
db: AsyncSession,
project_id: str,
organization_states: List[Dict[str, Any]],
chapter_number: int
) -> Dict[str, Any]:
"""
根据章节分析结果更新组织自身状态(势力等级、据点、宗旨等)
Args:
db: 数据库会话
project_id: 项目ID
organization_states: 组织状态变化列表(来自分析结果顶级字段)
chapter_number: 章节编号
Returns:
更新结果字典
"""
if not organization_states:
return {"updated_count": 0, "changes": []}
result = {"updated_count": 0, "changes": []}
logger.info(f"🏛️ 开始更新第{chapter_number}章的组织自身状态...")
# 预加载项目所有组织角色
all_chars_result = await db.execute(
select(Character).where(
Character.project_id == project_id,
Character.is_organization == True
)
)
org_chars = all_chars_result.scalars().all()
org_char_by_name: Dict[str, Character] = {c.name: c for c in org_chars}
# 预加载组织详情
char_ids = [c.id for c in org_chars]
if not char_ids:
logger.info("🏛️ 项目中无组织,跳过组织状态更新")
return result
orgs_result = await db.execute(
select(Organization).where(Organization.character_id.in_(char_ids))
)
all_orgs = orgs_result.scalars().all()
org_by_char_id: Dict[str, Organization] = {org.character_id: org for org in all_orgs}
for org_state in organization_states:
try:
org_name = org_state.get('organization_name')
if not org_name:
continue
org_char = org_char_by_name.get(org_name)
if not org_char:
logger.warning(f" ⚠️ 组织不存在: {org_name},跳过状态更新")
continue
organization = org_by_char_id.get(org_char.id)
if not organization:
logger.warning(f" ⚠️ 组织 {org_name} 无详情记录,跳过状态更新")
continue
updated = False
change_parts = []
# 检查组织是否被覆灭
is_destroyed = org_state.get('is_destroyed', False)
if is_destroyed:
# 组织覆灭:级联处理
org_char.status = 'destroyed'
org_char.status_changed_chapter = chapter_number
org_char.current_state = f"覆灭(第{chapter_number}章)"
org_char.state_updated_chapter = chapter_number
organization.power_level = 0
# 所有活跃成员标记为retired
members_result = await db.execute(
select(OrganizationMember).where(
and_(
OrganizationMember.organization_id == organization.id,
OrganizationMember.status == 'active'
)
)
)
active_members = members_result.scalars().all()
for member in active_members:
member.status = 'retired'
member.left_at = f"{chapter_number}"
member.notes = (
f"{member.notes or ''}\n[第{chapter_number}章] 组织覆灭"
).strip()
key_event = org_state.get('key_event', '')
event_desc = f"{key_event[:40]}" if key_event else ""
result["updated_count"] += 1
change_summary = f"💀 {org_name} 覆灭{event_desc}{len(active_members)}名成员受影响"
result["changes"].append(change_summary)
logger.info(f" 💀 {change_summary}")
continue # 覆灭后不再更新其他属性
# 势力等级变化
power_change = org_state.get('power_change', 0)
if power_change and isinstance(power_change, (int, float)):
old_power = organization.power_level or 50
new_power = max(0, min(100, old_power + int(power_change)))
if new_power != old_power:
organization.power_level = new_power
change_parts.append(f"势力:{old_power}{new_power}")
updated = True
# 据点变化
new_location = org_state.get('new_location')
if new_location and isinstance(new_location, str):
old_location = organization.location or '未设定'
organization.location = new_location
change_parts.append(f"据点:{old_location}{new_location}")
updated = True
# 宗旨/目标变化
new_purpose = org_state.get('new_purpose')
if new_purpose and isinstance(new_purpose, str):
old_purpose = (org_char.organization_purpose or '未设定')[:30]
org_char.organization_purpose = new_purpose
change_parts.append(f"宗旨变更")
updated = True
# 状态描述 -> 更新到 Character 的 current_state
status_desc = org_state.get('status_description')
if status_desc and isinstance(status_desc, str):
org_char.current_state = status_desc
org_char.state_updated_chapter = chapter_number
if not change_parts: # 如果只有状态描述没有其他变化
change_parts.append(f"状态:{status_desc[:30]}")
updated = True
if updated:
result["updated_count"] += 1
key_event = org_state.get('key_event', '')
change_summary = f"🏛️ {org_name} 状态变化: {', '.join(change_parts)}"
if key_event:
change_summary += f" (因:{key_event[:40]})"
result["changes"].append(change_summary)
logger.info(f"{change_summary}")
except Exception as item_error:
logger.error(
f" ❌ 更新组织 {org_state.get('organization_name', '未知')} 状态失败: {str(item_error)}"
)
if result["updated_count"] > 0:
await db.commit()
logger.info(f"✅ 组织状态更新完成: {result['updated_count']}个组织")
return result
@staticmethod
def _calculate_intimacy_delta(change_desc: str) -> int:
"""
根据变化描述计算亲密度调整值
Args:
change_desc: 关系变化描述文本
Returns:
亲密度调整值
"""
delta = 0
matched = False
for keyword, adjustment in INTIMACY_ADJUSTMENTS.items():
if keyword in change_desc:
delta += adjustment
matched = True
# 限制单次调整幅度
if matched:
delta = max(-30, min(30, delta))
return delta