lty/qy_lty/userapp/management/commands/seed_affinity.py
pmc 2d82b2ef7f
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 8m44s
feat: implement affinity (favorability) system
- Add affinity level/setting models and migrations
- Migrate favorability data to UserDevice
- Add management commands for userapp
- Add admin CLAUDE.md and docs
- Update affinity system design doc and task checklist
- Update device_interaction and userapp models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:18:30 +08:00

184 lines
7.0 KiB
Python
Raw 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.

"""P1-10 默认数据 seed 命令
用法:
python manage.py seed_affinity # 写入默认数据,已存在则跳过
python manage.py seed_affinity --force # 强制覆盖已存在的默认规则/等级
写入内容(与「好感度系统功能与规则设计.md」§4.2 / §6.2 一致):
1. AffinitySetting 单例(如不存在)
2. 8 条默认互动规则
3. 5 个默认等级
幂等性:
默认按 rule_key规则/ level等级查询已存在则跳过。
--force 模式下覆盖已存在记录的字段(不删旧记录)。
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from userapp.models import (
AffinityRule,
AffinityLevel,
AffinitySetting,
)
# 默认规则,与设计文档 §4.2 一致
DEFAULT_RULES = [
{
'rule_key': 'card', 'name': '使用卡片', 'description': '用户使用洛天依卡片',
'trigger_type': 'action',
'min_change': 1, 'max_change': 3, 'single_cap': 3, 'daily_cap': 10,
'cooldown_seconds': 0, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'chat', 'name': '对话', 'description': '与洛天依进行对话',
'trigger_type': 'action',
'min_change': 1, 'max_change': 5, 'single_cap': 5, 'daily_cap': 15,
'cooldown_seconds': 30, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'feed', 'name': '喂食', 'description': '给洛天依喂食',
'trigger_type': 'action',
'min_change': 2, 'max_change': 8, 'single_cap': 8, 'daily_cap': 16,
'cooldown_seconds': 0, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'touch', 'name': '抚摸', 'description': '抚摸洛天依',
'trigger_type': 'action',
'min_change': 1, 'max_change': 3, 'single_cap': 3, 'daily_cap': 9,
'cooldown_seconds': 10, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'dress', 'name': '换装', 'description': '为洛天依更换服装',
'trigger_type': 'action',
'min_change': 2, 'max_change': 6, 'single_cap': 6, 'daily_cap': 12,
'cooldown_seconds': 0, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'prop', 'name': '使用道具', 'description': '使用互动道具',
'trigger_type': 'action',
'min_change': 1, 'max_change': 4, 'single_cap': 4, 'daily_cap': 12,
'cooldown_seconds': 0, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'gift', 'name': '送礼物', 'description': '赠送礼物给洛天依',
'trigger_type': 'action',
'min_change': 5, 'max_change': 15, 'single_cap': 15, 'daily_cap': 20,
'cooldown_seconds': 0, 'is_negative': False, 'is_enabled': True,
},
{
'rule_key': 'decay', 'name': '无互动衰减', 'description': '长时间不互动导致好感度下降',
'trigger_type': 'decay',
'min_change': -3, 'max_change': -1, 'single_cap': 3, 'daily_cap': 5,
'cooldown_seconds': 0, 'is_negative': True, 'is_enabled': True,
},
]
# 默认等级,与设计文档 §6.2 一致
DEFAULT_LEVELS = [
{
'level': 1, 'name': '初识', 'min_affinity': 0, 'max_affinity': 20,
'unlock_content': '基础对话功能',
'reward_type': 'unlock', 'reward_currency': 0, 'reward_items': [],
'is_enabled': True,
},
{
'level': 2, 'name': '相识', 'min_affinity': 21, 'max_affinity': 40,
'unlock_content': '基础服装、道具使用',
'reward_type': 'unlock', 'reward_currency': 0, 'reward_items': [],
'is_enabled': True,
},
{
'level': 3, 'name': '熟悉', 'min_affinity': 41, 'max_affinity': 60,
'unlock_content': '更多服装、特殊对话',
'reward_type': 'unlock', 'reward_currency': 0, 'reward_items': [],
'is_enabled': True,
},
{
'level': 4, 'name': '亲密', 'min_affinity': 61, 'max_affinity': 80,
'unlock_content': '限定服装、特殊互动',
'reward_type': 'unlock', 'reward_currency': 0, 'reward_items': [],
'is_enabled': True,
},
{
'level': 5, 'name': '挚友', 'min_affinity': 81, 'max_affinity': 100,
'unlock_content': '专属内容、特殊剧情',
'reward_type': 'unlock', 'reward_currency': 0, 'reward_items': [],
'is_enabled': True,
},
]
class Command(BaseCommand):
help = '初始化好感度系统默认数据AffinitySetting / AffinityRule / AffinityLevel'
def add_arguments(self, parser):
parser.add_argument(
'--force', action='store_true',
help='强制覆盖已存在记录的字段(不删旧记录)',
)
@transaction.atomic
def handle(self, *args, **options):
force = options['force']
self._seed_setting()
rules_created, rules_updated = self._seed_rules(force)
levels_created, levels_updated = self._seed_levels(force)
self.stdout.write(self.style.SUCCESS(
f'\n[seed_affinity] 完成:'
f'规则 创建 {rules_created} 更新 {rules_updated}'
f'等级 创建 {levels_created} 更新 {levels_updated}'
))
def _seed_setting(self):
if AffinitySetting.objects.exists():
self.stdout.write('AffinitySetting 已存在,跳过')
return
AffinitySetting.objects.create()
self.stdout.write(self.style.SUCCESS('AffinitySetting 已创建(默认值)'))
def _seed_rules(self, force):
created = 0
updated = 0
for spec in DEFAULT_RULES:
rule_key = spec['rule_key']
existing = AffinityRule.objects.filter(rule_key=rule_key).first()
if existing is None:
AffinityRule.objects.create(**spec)
created += 1
self.stdout.write(f' + 规则 {rule_key} 已创建')
elif force:
for k, v in spec.items():
setattr(existing, k, v)
existing.save()
updated += 1
self.stdout.write(f' ~ 规则 {rule_key} 已覆盖')
else:
self.stdout.write(f' - 规则 {rule_key} 已存在,跳过')
return created, updated
def _seed_levels(self, force):
created = 0
updated = 0
for spec in DEFAULT_LEVELS:
level_num = spec['level']
existing = AffinityLevel.objects.filter(level=level_num).first()
if existing is None:
AffinityLevel.objects.create(**spec)
created += 1
self.stdout.write(f' + 等级 Lv{level_num} 已创建')
elif force:
for k, v in spec.items():
setattr(existing, k, v)
existing.save()
updated += 1
self.stdout.write(f' ~ 等级 Lv{level_num} 已覆盖')
else:
self.stdout.write(f' - 等级 Lv{level_num} 已存在,跳过')
return created, updated