import uuid from django.conf import settings from django.db import models class MathIdentity(models.Model): code = models.CharField(max_length=8, unique=True) name = models.CharField(max_length=80) clan = models.CharField(max_length=40) mathematician = models.CharField(max_length=80) description = models.TextField(blank=True) initial_abilities = models.JSONField(default=dict, blank=True) portrait = models.CharField(max_length=200, blank=True) def __str__(self): return f"{self.code} {self.name}" class MathBTIAssessment(models.Model): version = models.CharField(max_length=30, unique=True) title = models.CharField(max_length=120) definition = models.JSONField() is_published = models.BooleanField(default=False) published_at = models.DateTimeField(null=True, blank=True) def __str__(self): return self.title class MathBTIResult(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="mathbti_results") assessment = models.ForeignKey(MathBTIAssessment, on_delete=models.PROTECT) identity = models.ForeignKey(MathIdentity, on_delete=models.PROTECT) answers = models.JSONField(default=list) axis_scores = models.JSONField(default=dict) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] class Character(models.Model): slug = models.SlugField(unique=True) name = models.CharField(max_length=80) profile = models.TextField(blank=True) metadata = models.JSONField(default=dict, blank=True) def __str__(self): return self.name class Story(models.Model): class Kind(models.TextChoices): FLAGSHIP = "flagship", "旗舰人生" SKILL = "skill", "人物 Skill" SPECIAL = "special", "特别篇" slug = models.SlugField(unique=True) title = models.CharField(max_length=120) summary = models.TextField(blank=True) kind = models.CharField(max_length=16, choices=Kind.choices, default=Kind.FLAGSHIP) estimated_minutes = models.PositiveIntegerField(default=20) is_visible = models.BooleanField(default=True) def __str__(self): return self.title class StoryVersion(models.Model): story = models.ForeignKey(Story, on_delete=models.CASCADE, related_name="versions") version = models.PositiveIntegerField() content = models.JSONField() is_published = models.BooleanField(default=False) published_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: constraints = [ models.UniqueConstraint(fields=("story", "version"), name="unique_story_version") ] ordering = ["story", "-version"] def __str__(self): return f"{self.story} v{self.version}" class StoryRun(models.Model): class Status(models.TextChoices): ACTIVE = "active", "进行中" COMPLETED = "completed", "已完成" ABANDONED = "abandoned", "已放弃" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="story_runs") story_version = models.ForeignKey(StoryVersion, on_delete=models.PROTECT, related_name="runs") current_node = models.CharField(max_length=100) state = models.JSONField(default=dict) status = models.CharField(max_length=16, choices=Status.choices, default=Status.ACTIVE) ending_code = models.CharField(max_length=80, blank=True) started_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) completed_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ["-updated_at"] class StoryChoice(models.Model): run = models.ForeignKey(StoryRun, on_delete=models.CASCADE, related_name="choices") sequence = models.PositiveIntegerField() idempotency_key = models.CharField(max_length=80, null=True, blank=True) node_id = models.CharField(max_length=100) choice_index = models.PositiveIntegerField() choice_text = models.CharField(max_length=300) effects = models.JSONField(default=dict, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: constraints = [ models.UniqueConstraint(fields=("run", "sequence"), name="unique_run_choice_sequence"), models.UniqueConstraint( fields=("run", "idempotency_key"), name="unique_run_choice_idempotency" ), ] ordering = ["sequence"] class StoryMark(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="story_marks", ) story = models.ForeignKey( Story, on_delete=models.CASCADE, related_name="collected_marks", ) source_run = models.ForeignKey( StoryRun, null=True, blank=True, on_delete=models.SET_NULL, related_name="acquired_marks", ) code = models.CharField(max_length=60) name = models.CharField(max_length=80) domain = models.CharField(max_length=40) acquired_at = models.DateTimeField(auto_now_add=True) class Meta: constraints = [ models.UniqueConstraint( fields=("user", "story", "code"), name="unique_user_story_mark", ) ] ordering = ["acquired_at"] def __str__(self): return f"{self.user} · {self.name}" class UserRelationship(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) character = models.ForeignKey(Character, on_delete=models.CASCADE) trust = models.SmallIntegerField(default=0) rivalry = models.SmallIntegerField(default=0) debt = models.SmallIntegerField(default=0) cooperation = models.SmallIntegerField(default=0) metadata = models.JSONField(default=dict, blank=True) class Meta: constraints = [ models.UniqueConstraint(fields=("user", "character"), name="unique_user_character") ] class SkillPackage(models.Model): story = models.OneToOneField(Story, on_delete=models.CASCADE, related_name="skill_package") real_person = models.CharField(max_length=120) historical_context = models.TextField() fact_sources = models.JSONField(default=list) fictional_scope = models.TextField() creator = models.CharField(max_length=120)