Implement tasks

This commit is contained in:
2026-03-02 12:45:24 +00:00
parent 6986c1b5ab
commit e1de6d016d
29 changed files with 2970 additions and 172 deletions

View File

@@ -1921,6 +1921,291 @@ class TranslationEventLog(models.Model):
]
class AnswerMemory(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="answer_memory")
service = models.CharField(max_length=255, choices=CHANNEL_SERVICE_CHOICES)
channel_identifier = models.CharField(max_length=255)
question_fingerprint = models.CharField(max_length=128)
question_text = models.TextField(blank=True, default="")
answer_message = models.ForeignKey(
Message,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="answer_memory_rows",
)
answer_text = models.TextField(blank=True, default="")
confidence_meta = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=["user", "service", "channel_identifier", "created_at"]),
models.Index(fields=["user", "question_fingerprint", "created_at"]),
]
class AnswerSuggestionEvent(models.Model):
STATUS_CHOICES = (
("suggested", "Suggested"),
("accepted", "Accepted"),
("dismissed", "Dismissed"),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="answer_suggestion_events",
)
message = models.ForeignKey(
Message,
on_delete=models.CASCADE,
related_name="answer_suggestion_events",
)
status = models.CharField(max_length=32, choices=STATUS_CHOICES, default="suggested")
candidate_answer = models.ForeignKey(
AnswerMemory,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="suggestion_events",
)
score = models.FloatField(default=0.0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
indexes = [
models.Index(fields=["user", "status", "created_at"]),
models.Index(fields=["message", "status"]),
]
class TaskProject(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="task_projects")
name = models.CharField(max_length=255)
external_key = models.CharField(max_length=255, blank=True, default="")
active = models.BooleanField(default=True)
settings = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "name"],
name="unique_task_project_name_per_user",
)
]
indexes = [models.Index(fields=["user", "active", "updated_at"])]
class TaskEpic(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
project = models.ForeignKey(
TaskProject,
on_delete=models.CASCADE,
related_name="epics",
)
name = models.CharField(max_length=255)
external_key = models.CharField(max_length=255, blank=True, default="")
active = models.BooleanField(default=True)
settings = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["project", "name"],
name="unique_task_epic_name_per_project",
)
]
indexes = [models.Index(fields=["project", "active", "updated_at"])]
class ChatTaskSource(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="chat_task_sources")
service = models.CharField(max_length=255, choices=CHANNEL_SERVICE_CHOICES)
channel_identifier = models.CharField(max_length=255)
project = models.ForeignKey(
TaskProject,
on_delete=models.CASCADE,
related_name="chat_sources",
)
epic = models.ForeignKey(
TaskEpic,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="chat_sources",
)
enabled = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
indexes = [
models.Index(fields=["user", "service", "channel_identifier", "enabled"]),
models.Index(fields=["project", "enabled"]),
]
class DerivedTask(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="derived_tasks")
project = models.ForeignKey(
TaskProject,
on_delete=models.CASCADE,
related_name="derived_tasks",
)
epic = models.ForeignKey(
TaskEpic,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="derived_tasks",
)
title = models.CharField(max_length=255)
source_service = models.CharField(max_length=255, choices=CHANNEL_SERVICE_CHOICES)
source_channel = models.CharField(max_length=255)
origin_message = models.ForeignKey(
Message,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="derived_task_origins",
)
reference_code = models.CharField(max_length=64, blank=True, default="")
external_key = models.CharField(max_length=255, blank=True, default="")
status_snapshot = models.CharField(max_length=64, blank=True, default="open")
immutable_payload = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=["user", "project", "created_at"]),
models.Index(fields=["user", "source_service", "source_channel"]),
models.Index(fields=["user", "reference_code"]),
]
class DerivedTaskEvent(models.Model):
EVENT_CHOICES = (
("created", "Created"),
("progress", "Progress"),
("completion_marked", "Completion Marked"),
("synced", "Synced"),
("sync_failed", "Sync Failed"),
("parse_warning", "Parse Warning"),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
task = models.ForeignKey(
DerivedTask,
on_delete=models.CASCADE,
related_name="events",
)
event_type = models.CharField(max_length=32, choices=EVENT_CHOICES)
actor_identifier = models.CharField(max_length=255, blank=True, default="")
source_message = models.ForeignKey(
Message,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="derived_task_events",
)
payload = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["created_at", "id"]
indexes = [
models.Index(fields=["task", "created_at"]),
models.Index(fields=["event_type", "created_at"]),
]
class ExternalSyncEvent(models.Model):
STATUS_CHOICES = (
("pending", "Pending"),
("ok", "OK"),
("failed", "Failed"),
("retrying", "Retrying"),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="external_sync_events")
task = models.ForeignKey(
DerivedTask,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="external_sync_events",
)
task_event = models.ForeignKey(
DerivedTaskEvent,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="external_sync_events",
)
provider = models.CharField(max_length=64, default="mock")
idempotency_key = models.CharField(max_length=255, blank=True, default="")
status = models.CharField(max_length=32, choices=STATUS_CHOICES, default="pending")
payload = models.JSONField(default=dict, blank=True)
error = models.TextField(blank=True, default="")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
indexes = [
models.Index(fields=["user", "provider", "status", "updated_at"]),
models.Index(fields=["idempotency_key"]),
]
class TaskProviderConfig(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="task_provider_configs")
provider = models.CharField(max_length=64, default="mock")
enabled = models.BooleanField(default=False)
settings = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "provider"],
name="unique_task_provider_config_per_user",
)
]
class TaskCompletionPattern(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="task_completion_patterns")
phrase = models.CharField(max_length=64)
enabled = models.BooleanField(default=True)
position = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "phrase"],
name="unique_task_completion_phrase_per_user",
)
]
indexes = [models.Index(fields=["user", "enabled", "position"])]
# class Perms(models.Model):
# class Meta:
# permissions = (