feat: complete multimodal ticket intake
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_intake_statuses(apps, schema_editor):
|
||||
TicketDraft = apps.get_model("tickets", "TicketDraft")
|
||||
TicketDraft.objects.filter(status="editing").update(status="collecting")
|
||||
TicketDraft.objects.filter(status="ready").update(status="needs_input")
|
||||
TicketDraft.objects.filter(status="confirmed").update(status="ticket_created")
|
||||
|
||||
|
||||
def restore_draft_statuses(apps, schema_editor):
|
||||
TicketDraft = apps.get_model("tickets", "TicketDraft")
|
||||
TicketDraft.objects.filter(status="collecting").update(status="editing")
|
||||
TicketDraft.objects.filter(status="needs_input").update(status="ready")
|
||||
TicketDraft.objects.filter(status="ticket_created").update(status="confirmed")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("tickets", "0005_ticketcollaborator_ticketfieldevidence_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="ticketdraft",
|
||||
name="field_evidence",
|
||||
field=models.JSONField(default=dict),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraft",
|
||||
name="invalid_reason",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraft",
|
||||
name="message_timeline",
|
||||
field=models.JSONField(default=list),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraft",
|
||||
name="problem_valid",
|
||||
field=models.BooleanField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ticketdraft",
|
||||
name="status",
|
||||
field=models.CharField(default="collecting", max_length=24),
|
||||
),
|
||||
migrations.RunPython(migrate_intake_statuses, restore_draft_statuses),
|
||||
]
|
||||
100
backend/apps/tickets/migrations/0007_ticketdraftmaterial.py
Normal file
100
backend/apps/tickets/migrations/0007_ticketdraftmaterial.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("tickets", "0006_ticketdraft_intake_result"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="ticketattachment",
|
||||
name="parse_error",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketattachment",
|
||||
name="parse_status",
|
||||
field=models.CharField(default="pending", max_length=24),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketattachment",
|
||||
name="scan_result",
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketattachment",
|
||||
name="scan_status",
|
||||
field=models.CharField(default="pending", max_length=24),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketattachment",
|
||||
name="sha256",
|
||||
field=models.CharField(blank=True, max_length=64),
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="TicketDraftImage",
|
||||
new_name="TicketDraftMaterial",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="draft",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="materials",
|
||||
to="tickets.ticketdraft",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="confirmed_attachment",
|
||||
field=models.OneToOneField(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="source_draft_material",
|
||||
to="tickets.ticketattachment",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="claimed_mime_type",
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="extension",
|
||||
field=models.CharField(blank=True, max_length=16),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="material_type",
|
||||
field=models.CharField(default="image", max_length=24),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="parse_error",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="parse_status",
|
||||
field=models.CharField(default="pending", max_length=24),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="parsed_text",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="scan_result",
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ticketdraftmaterial",
|
||||
name="scan_status",
|
||||
field=models.CharField(default="pending", max_length=24),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-27 02:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0007_ticketdraftmaterial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TicketMessageTimelineItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sequence', models.PositiveIntegerField()),
|
||||
('speaker', models.CharField(blank=True, max_length=120)),
|
||||
('role', models.CharField(default='unknown', max_length=24)),
|
||||
('content', models.TextField()),
|
||||
('occurred_at', models.DateTimeField(blank=True, null=True)),
|
||||
('occurred_at_raw', models.CharField(blank=True, max_length=120)),
|
||||
('source_ref', models.CharField(blank=True, max_length=160)),
|
||||
('confidence', models.DecimalField(blank=True, decimal_places=4, max_digits=5, null=True)),
|
||||
('conflict', models.TextField(blank=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('draft', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='timeline_items', to='tickets.ticketdraft')),
|
||||
('source_material', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='message_timeline_items', to='tickets.ticketdraftmaterial')),
|
||||
('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='message_timeline_items', to='tickets.ticket')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['sequence'],
|
||||
'indexes': [models.Index(fields=['ticket', 'sequence'], name='tickets_tic_ticket__20b0c4_idx'), models.Index(fields=['draft', 'sequence'], name='tickets_tic_draft_i_0adfa1_idx')],
|
||||
'constraints': [models.UniqueConstraint(fields=('draft', 'sequence'), name='unique_ticket_draft_timeline_sequence')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,83 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-27 03:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.operations import TrigramExtension
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0008_ticketmessagetimelineitem'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
TrigramExtension(),
|
||||
migrations.CreateModel(
|
||||
name='IntakeEvaluationCase',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.SlugField(max_length=120, unique=True)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('source_kind', models.CharField(default='synthetic', max_length=32)),
|
||||
('valid_input', models.BooleanField(default=True)),
|
||||
('input_manifest', models.JSONField(default=dict)),
|
||||
('expected_fields', models.JSONField(default=dict)),
|
||||
('expected_timeline', models.JSONField(default=list)),
|
||||
('expected_auto_created', models.BooleanField(default=True)),
|
||||
('expected_similar_ticket', models.BooleanField(default=False)),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['key'],
|
||||
'indexes': [models.Index(fields=['source_kind', 'enabled'], name='tickets_int_source__74e817_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IntakeEvaluationResult',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('actual_fields', models.JSONField(default=dict)),
|
||||
('actual_auto_created', models.BooleanField(default=False)),
|
||||
('correct_field_count', models.PositiveIntegerField(default=0)),
|
||||
('evaluated_field_count', models.PositiveIntegerField(default=0)),
|
||||
('field_accuracy', models.DecimalField(blank=True, decimal_places=5, max_digits=6, null=True)),
|
||||
('passed', models.BooleanField(default=False)),
|
||||
('details', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('case', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='results', to='tickets.intakeevaluationcase')),
|
||||
('draft', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='evaluation_results', to='tickets.ticketdraft')),
|
||||
('reviewed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_intake_evaluations', to=settings.AUTH_USER_MODEL)),
|
||||
('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='intake_evaluation_results', to='tickets.ticket')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['case', 'created_at'], name='tickets_int_case_id_8c9c27_idx')],
|
||||
'constraints': [models.UniqueConstraint(fields=('case', 'draft'), name='unique_intake_case_draft_result')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TicketSimilaritySuggestion',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('score', models.DecimalField(decimal_places=5, max_digits=6)),
|
||||
('title_similarity', models.DecimalField(decimal_places=5, default=0, max_digits=6)),
|
||||
('keyword_score', models.DecimalField(decimal_places=5, default=0, max_digits=6)),
|
||||
('matched_excerpt', models.TextField(blank=True)),
|
||||
('algorithm_version', models.CharField(default='fts_trigram_v1', max_length=40)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('candidate_ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='suggested_for_tickets', to='tickets.ticket')),
|
||||
('source_ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='similarity_suggestions', to='tickets.ticket')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-score', 'candidate_ticket_id'],
|
||||
'indexes': [models.Index(fields=['source_ticket', 'algorithm_version', 'score'], name='tickets_tic_source__88b288_idx')],
|
||||
'constraints': [models.UniqueConstraint(fields=('source_ticket', 'candidate_ticket', 'algorithm_version'), name='unique_ticket_similarity_suggestion'), models.CheckConstraint(condition=models.Q(('source_ticket', models.F('candidate_ticket')), _negated=True), name='ticket_similarity_not_self')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,58 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-27 03:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0009_intakeevaluationcase_intakeevaluationresult_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='attempted_actions',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='current_result',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='customer_request',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='error_information',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='module_name',
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='product_name',
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='tags',
|
||||
field=models.JSONField(blank=True, default=list),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticketmessagetimelineitem',
|
||||
name='timezone',
|
||||
field=models.CharField(blank=True, max_length=80),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticketsimilaritysuggestion',
|
||||
name='algorithm_version',
|
||||
field=models.CharField(default='fts_trigram_v2_context', max_length=40),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-27 03:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0010_ticket_attempted_actions_ticket_current_result_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='intakeevaluationresult',
|
||||
options={'ordering': ['-updated_at']},
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='intakeevaluationresult',
|
||||
name='tickets_int_case_id_8c9c27_idx',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='intakeevaluationresult',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='intakeevaluationresult',
|
||||
index=models.Index(
|
||||
fields=['case', 'updated_at'],
|
||||
name='tickets_int_case_id_79c7c4_idx',
|
||||
),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user