feat: add intelligent support workflow

This commit is contained in:
hailin
2026-07-27 19:00:35 +08:00
parent 93065c0a00
commit 1a49ce4eae
38 changed files with 5644 additions and 33 deletions

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.2.15 on 2026-07-27 04:04
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agents', '0004_agentrunevent'),
('tickets', '0011_alter_intakeevaluationresult_options_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='TicketSupportAnalysis',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('query', models.TextField()),
('references', models.JSONField(default=list)),
('retrieval_policy', models.JSONField(default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_ticket_support_analyses', to=settings.AUTH_USER_MODEL)),
('run', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_support_analysis', to='agents.agentrun')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='support_analyses', to='tickets.ticket')),
],
options={
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['ticket', 'created_at'], name='tickets_tic_ticket__d2d551_idx')],
},
),
]

View File

@@ -0,0 +1,84 @@
# Generated by Django 5.2.15 on 2026-07-27 04:58
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('solutions', '0003_solution_allow_auto_resolve_and_more'),
('tickets', '0012_ticketsupportanalysis'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='ticketsupportanalysis',
name='applied_solution',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applied_support_analyses', to='solutions.solution'),
),
migrations.AddField(
model_name='ticketsupportanalysis',
name='automation_decision',
field=models.JSONField(blank=True, default=dict),
),
migrations.AddField(
model_name='ticketsupportanalysis',
name='processed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.CreateModel(
name='CustomerReplyTask',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(default='draft', max_length=24)),
('draft_text', models.TextField()),
('final_text', models.TextField()),
('revision', models.PositiveIntegerField(default=1)),
('sent_at', models.DateTimeField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_customer_reply_tasks', to=settings.AUTH_USER_MODEL)),
('sent_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_customer_reply_tasks', to=settings.AUTH_USER_MODEL)),
('support_analysis', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_reply_tasks', to='tickets.ticketsupportanalysis')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer_reply_tasks', to='tickets.ticket')),
('updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_customer_reply_tasks', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='CustomerReplyRevision',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.PositiveIntegerField()),
('content', models.TextField()),
('source', models.CharField(default='human', max_length=24)),
('created_at', models.DateTimeField(auto_now_add=True)),
('edited_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_reply_revisions', to=settings.AUTH_USER_MODEL)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='tickets.customerreplytask')),
],
options={
'ordering': ['version'],
},
),
migrations.AddIndex(
model_name='customerreplytask',
index=models.Index(fields=['ticket', 'status', 'created_at'], name='tickets_cus_ticket__31c637_idx'),
),
migrations.AddIndex(
model_name='customerreplytask',
index=models.Index(fields=['status', 'updated_at'], name='tickets_cus_status_7bec2e_idx'),
),
migrations.AddConstraint(
model_name='customerreplytask',
constraint=models.UniqueConstraint(condition=models.Q(('status', 'draft')), fields=('ticket',), name='unique_active_customer_reply_task'),
),
migrations.AddConstraint(
model_name='customerreplyrevision',
constraint=models.UniqueConstraint(fields=('task', 'version'), name='unique_customer_reply_revision'),
),
]

View File

@@ -0,0 +1,41 @@
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("tickets", "0013_ticketsupportanalysis_applied_solution_and_more"),
]
operations = [
migrations.AddField(
model_name="customerreplytask",
name="approved_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="customerreplytask",
name="approved_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="approved_customer_reply_tasks",
to=settings.AUTH_USER_MODEL,
),
),
migrations.RemoveConstraint(
model_name="customerreplytask",
name="unique_active_customer_reply_task",
),
migrations.AddConstraint(
model_name="customerreplytask",
constraint=models.UniqueConstraint(
condition=models.Q(("status__in", ["draft", "approved"])),
fields=("ticket",),
name="unique_active_customer_reply_task",
),
),
]

View File

@@ -0,0 +1,113 @@
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("tickets", "0014_customerreplytask_channel_approval"),
]
operations = [
migrations.CreateModel(
name="SupportEvaluationCase",
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)),
("input_manifest", models.JSONField(default=dict)),
("expected_solution_ids", models.JSONField(default=list)),
("expected_auto_resolve", models.BooleanField(default=False)),
("expected_manual_fallback", models.BooleanField(default=True)),
("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_sup_source__864444_idx",
)
],
},
),
migrations.CreateModel(
name="SupportEvaluationResult",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("actual_candidate_solution_ids", models.JSONField(default=list)),
("actual_citation_ranks", models.JSONField(default=list)),
("matched_expected_solution", models.BooleanField(default=False)),
("actual_auto_resolved", models.BooleanField(default=False)),
("actual_manual_fallback", models.BooleanField(default=False)),
("incorrect_auto_reuse", models.BooleanField(default=False)),
("citations_valid", models.BooleanField(default=False)),
("passed", models.BooleanField(default=False)),
("details", models.JSONField(blank=True, default=dict)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"analysis",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="evaluation_results",
to="tickets.ticketsupportanalysis",
),
),
(
"case",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="results",
to="tickets.supportevaluationcase",
),
),
(
"reviewed_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="reviewed_support_evaluations",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ["-updated_at"],
"indexes": [
models.Index(
fields=["case", "updated_at"],
name="tickets_sup_case_id_1fef4b_idx",
)
],
"constraints": [
models.UniqueConstraint(
fields=("case", "analysis"),
name="unique_support_case_analysis_result",
)
],
},
),
]