All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 8m44s
- 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>
184 lines
7.0 KiB
Python
184 lines
7.0 KiB
Python
"""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
|