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

145 lines
6.2 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.

from django.db import models
from userapp.models import ParadiseUser
import uuid
# Create your models here.
class DeviceType(models.Model):
"""设备类型"""
name = models.CharField('类型名称', max_length=100, unique=True)
code = models.CharField('类型代码', max_length=10, unique=True)
description = models.TextField('类型描述', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '设备类型'
verbose_name_plural = '设备类型'
ordering = ['name']
def __str__(self):
return f"{self.name} ({self.code})"
class DeviceBatch(models.Model):
"""设备批次"""
device_type = models.ForeignKey(DeviceType, on_delete=models.CASCADE, verbose_name='设备类型', related_name='batches')
batch_number = models.CharField('批次号', max_length=20, unique=True)
production_date = models.DateField('生产日期')
quantity = models.PositiveIntegerField('数量')
description = models.TextField('批次描述', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '设备批次'
verbose_name_plural = '设备批次'
ordering = ['-production_date']
def __str__(self):
return f"{self.device_type.name} - {self.batch_number}"
class Device(models.Model):
"""设备表"""
device_type = models.ForeignKey(DeviceType, on_delete=models.CASCADE, verbose_name='设备类型', related_name='devices')
batch = models.ForeignKey(DeviceBatch, on_delete=models.CASCADE, verbose_name='设备批次', related_name='devices')
serial_number = models.CharField('序列号', max_length=50)
device_code = models.CharField('设备码', max_length=100, unique=True)
mac_address = models.CharField('MAC地址', max_length=17, unique=True)
is_active = models.BooleanField('是否激活', default=False)
activated_at = models.DateTimeField('激活时间', null=True, blank=True)
# 设备状态信息
STATUS_CHOICES = (
('connected', '已连接'),
('disconnected', '未连接'),
)
status = models.CharField('设备状态', max_length=20, choices=STATUS_CHOICES, default='disconnected')
battery_level = models.IntegerField('电量', default=0, help_text='电池电量百分比(0-100)')
firmware_version = models.CharField('固件版本号', max_length=50, blank=True, null=True)
# WiFi设置
wifi_name = models.CharField('WiFi名称', max_length=100, blank=True, null=True)
wifi_password = models.CharField('WiFi密码', max_length=100, blank=True, null=True)
# 设备设置
brightness = models.IntegerField('亮度', default=50, help_text='屏幕亮度(0-100)')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '设备'
verbose_name_plural = '设备'
ordering = ['-created_at']
unique_together = ['device_type', 'batch', 'serial_number']
def __str__(self):
return self.device_code
def save(self, *args, **kwargs):
# 如果没有设备码,自动生成
if not self.device_code:
self.device_code = self.generate_device_code()
super().save(*args, **kwargs)
def generate_device_code(self):
"""生成唯一设备码"""
# 格式: 类型代码-批次号-序列号
return f"{self.device_type.code}-{self.batch.batch_number}-{self.serial_number}"
class UserDevice(models.Model):
"""用户设备关联表
P1-08 扩展:好感度变更为「设备级」,每条 UserDevice 绑定独立维护
favorability: 当前好感度值
affinity_level: 当前等级缓存(由服务端计算)
last_active_at: 最近一次互动时间,用于衰减判断
is_active: 软删除标记。解绑置为 false重绑可读取历史值
"""
user = models.ForeignKey(ParadiseUser, on_delete=models.CASCADE, verbose_name='用户', related_name='devices')
device = models.ForeignKey(Device, on_delete=models.CASCADE, verbose_name='设备', related_name='users')
nickname = models.CharField('设备昵称', max_length=100, blank=True, null=True)
bound_at = models.DateTimeField('绑定时间', auto_now_add=True)
is_primary = models.BooleanField('是否主要设备', default=False)
# P1-08 好感度相关字段(设备级)
favorability = models.IntegerField(
'好感度', default=10,
help_text='设备级好感度,每台设备独立。初始值取自 AffinitySetting.initial_affinity'
)
affinity_level = models.IntegerField(
'当前等级', default=1,
help_text='缓存值,由服务端在好感度变化时同步更新'
)
last_active_at = models.DateTimeField(
'最近互动时间', null=True, blank=True, db_index=True,
help_text='用于衰减判断;服务端在每次成功 apply 时刷新'
)
is_active = models.BooleanField(
'绑定有效', default=True,
help_text='软删除标记。解绑置为 false重绑时可读取历史值。'
'注意:与 Device.is_active设备激活态不是同一概念'
)
class Meta:
verbose_name = '用户设备'
verbose_name_plural = '用户设备'
ordering = ['-bound_at']
unique_together = ['user', 'device']
def __str__(self):
return f"{self.user.username} - {self.device.device_code}"
def save(self, *args, **kwargs):
# 如果是该用户的第一个设备,设为主要设备
if not UserDevice.objects.filter(user=self.user).exists():
self.is_primary = True
# 如果设置为主要设备,取消其他设备的主要设备标记
if self.is_primary:
UserDevice.objects.filter(user=self.user).update(is_primary=False)
super().save(*args, **kwargs)