feat(regulatory): 新增法规核查模型与工作流通用字段
This commit is contained in:
@@ -4,7 +4,14 @@ from review_agent.models import FileSummaryBatch, WorkflowEvent
|
|||||||
|
|
||||||
|
|
||||||
def record_event(batch: FileSummaryBatch, event_type: str, payload: dict | None = None) -> WorkflowEvent:
|
def record_event(batch: FileSummaryBatch, event_type: str, payload: dict | None = None) -> WorkflowEvent:
|
||||||
return WorkflowEvent.objects.create(batch=batch, event_type=event_type, payload=payload or {})
|
return WorkflowEvent.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
workflow_type="file_summary",
|
||||||
|
workflow_batch_id=batch.pk,
|
||||||
|
conversation=batch.conversation,
|
||||||
|
event_type=event_type,
|
||||||
|
payload=payload or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def serialize_event(event: WorkflowEvent) -> dict[str, object]:
|
def serialize_event(event: WorkflowEvent) -> dict[str, object]:
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ def generate_excel_export(batch: FileSummaryBatch) -> ExportedSummaryFile:
|
|||||||
workbook.save(path)
|
workbook.save(path)
|
||||||
exported = ExportedSummaryFile.objects.create(
|
exported = ExportedSummaryFile.objects.create(
|
||||||
batch=batch,
|
batch=batch,
|
||||||
|
workflow_type="file_summary",
|
||||||
|
workflow_batch_id=batch.pk,
|
||||||
|
export_category="summary",
|
||||||
export_type=ExportedSummaryFile.ExportType.EXCEL,
|
export_type=ExportedSummaryFile.ExportType.EXCEL,
|
||||||
file_name=path.name,
|
file_name=path.name,
|
||||||
storage_path=str(path),
|
storage_path=str(path),
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ def generate_markdown_report(batch: FileSummaryBatch) -> tuple[ExportedSummaryFi
|
|||||||
path.write_text(content, encoding="utf-8")
|
path.write_text(content, encoding="utf-8")
|
||||||
exported = ExportedSummaryFile.objects.create(
|
exported = ExportedSummaryFile.objects.create(
|
||||||
batch=batch,
|
batch=batch,
|
||||||
|
workflow_type="file_summary",
|
||||||
|
workflow_batch_id=batch.pk,
|
||||||
|
export_category="summary",
|
||||||
export_type=ExportedSummaryFile.ExportType.MARKDOWN,
|
export_type=ExportedSummaryFile.ExportType.MARKDOWN,
|
||||||
file_name=path.name,
|
file_name=path.name,
|
||||||
storage_path=str(path),
|
storage_path=str(path),
|
||||||
|
|||||||
@@ -283,11 +283,12 @@ def export_download(request, export_id: int):
|
|||||||
extra={"export_id": exported.pk, "storage_path": exported.storage_path},
|
extra={"export_id": exported.pk, "storage_path": exported.storage_path},
|
||||||
)
|
)
|
||||||
return JsonResponse({"error": "文件不存在。"}, status=404)
|
return JsonResponse({"error": "文件不存在。"}, status=404)
|
||||||
content_type = (
|
content_types = {
|
||||||
"text/markdown; charset=utf-8"
|
ExportedSummaryFile.ExportType.MARKDOWN: "text/markdown; charset=utf-8",
|
||||||
if exported.export_type == ExportedSummaryFile.ExportType.MARKDOWN
|
ExportedSummaryFile.ExportType.EXCEL: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
else "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
ExportedSummaryFile.ExportType.JSON: "application/json; charset=utf-8",
|
||||||
)
|
}
|
||||||
|
content_type = content_types.get(exported.export_type, "application/octet-stream")
|
||||||
logger.info(
|
logger.info(
|
||||||
"Export download started",
|
"Export download started",
|
||||||
extra={
|
extra={
|
||||||
|
|||||||
@@ -112,7 +112,14 @@ def create_file_summary_batch(
|
|||||||
attachment.save(update_fields=["upload_status"])
|
attachment.save(update_fields=["upload_status"])
|
||||||
|
|
||||||
for code, name, _skill_name in NODE_DEFINITIONS:
|
for code, name, _skill_name in NODE_DEFINITIONS:
|
||||||
WorkflowNodeRun.objects.create(batch=batch, node_code=code, node_name=name)
|
WorkflowNodeRun.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
workflow_type="file_summary",
|
||||||
|
workflow_batch_id=batch.pk,
|
||||||
|
node_group="file_summary",
|
||||||
|
node_code=code,
|
||||||
|
node_name=name,
|
||||||
|
)
|
||||||
|
|
||||||
record_event(batch, "workflow_created", {"batch_id": batch.pk, "batch_no": batch.batch_no})
|
record_event(batch, "workflow_created", {"batch_id": batch.pk, "batch_no": batch.batch_no})
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -0,0 +1,479 @@
|
|||||||
|
# Generated by Django 5.2.14 on 2026-06-06 16:22
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"review_agent",
|
||||||
|
"0002_fileattachment_filesummarybatch_exportedsummaryfile_and_more",
|
||||||
|
),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryArtifact",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"artifact_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("markdown", "Markdown"),
|
||||||
|
("excel", "Excel"),
|
||||||
|
("json", "JSON"),
|
||||||
|
("text", "文本"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=160)),
|
||||||
|
("storage_path", models.CharField(max_length=500)),
|
||||||
|
(
|
||||||
|
"content_hash",
|
||||||
|
models.CharField(blank=True, default="", max_length=128),
|
||||||
|
),
|
||||||
|
("metadata", models.JSONField(blank=True, default=dict)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_artifact",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryIssue",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("rule_code", models.CharField(blank=True, default="", max_length=120)),
|
||||||
|
(
|
||||||
|
"category",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("completeness", "完整性"),
|
||||||
|
("structure", "章节"),
|
||||||
|
("consistency", "一致性"),
|
||||||
|
("rag", "法规依据"),
|
||||||
|
],
|
||||||
|
max_length=40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"severity",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("blocking", "阻断项"),
|
||||||
|
("high", "高风险"),
|
||||||
|
("medium", "中风险"),
|
||||||
|
("low", "低风险"),
|
||||||
|
("info", "提示"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=255)),
|
||||||
|
("detail", models.TextField(blank=True, default="")),
|
||||||
|
("suggestion", models.TextField(blank=True, default="")),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("open", "待处理"),
|
||||||
|
("resolved", "已整改"),
|
||||||
|
("accepted", "已接受"),
|
||||||
|
],
|
||||||
|
default="open",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("evidence", models.JSONField(blank=True, default=dict)),
|
||||||
|
("citations", models.JSONField(blank=True, default=list)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_issue",
|
||||||
|
"ordering": ["severity", "id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryNotificationRecord",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"channel",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("mock", "模拟"), ("feishu", "飞书")],
|
||||||
|
default="mock",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("target", models.CharField(blank=True, default="", max_length=160)),
|
||||||
|
("payload", models.JSONField(blank=True, default=dict)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "待发送"),
|
||||||
|
("sent", "已发送"),
|
||||||
|
("failed", "失败"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("error_message", models.TextField(blank=True, default="")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("sent_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_notification_record",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryReviewBatch",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("batch_no", models.CharField(max_length=64, unique=True)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "待执行"),
|
||||||
|
("running", "执行中"),
|
||||||
|
("success", "成功"),
|
||||||
|
("failed", "失败"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("risk_summary", models.JSONField(blank=True, default=dict)),
|
||||||
|
("work_dir", models.CharField(blank=True, default="", max_length=500)),
|
||||||
|
("error_message", models.TextField(blank=True, default="")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("started_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
("finished_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_review_batch",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryRuleVersion",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("code", models.CharField(max_length=80, unique=True)),
|
||||||
|
("name", models.CharField(max_length=160)),
|
||||||
|
("yaml_path", models.CharField(max_length=500)),
|
||||||
|
("yaml_hash", models.CharField(max_length=128)),
|
||||||
|
(
|
||||||
|
"rag_collection",
|
||||||
|
models.CharField(blank=True, default="", max_length=120),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rag_index_version",
|
||||||
|
models.CharField(blank=True, default="", max_length=80),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rag_index_hash",
|
||||||
|
models.CharField(blank=True, default="", max_length=128),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("active", "启用"),
|
||||||
|
("outdated", "待更新"),
|
||||||
|
("disabled", "停用"),
|
||||||
|
],
|
||||||
|
default="active",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_rule_version",
|
||||||
|
"ordering": ["-updated_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="export_category",
|
||||||
|
field=models.CharField(blank=True, default="summary", max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="workflow_batch_id",
|
||||||
|
field=models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="workflow_type",
|
||||||
|
field=models.CharField(blank=True, default="file_summary", max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowevent",
|
||||||
|
name="conversation",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="workflow_events",
|
||||||
|
to="review_agent.conversation",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowevent",
|
||||||
|
name="workflow_batch_id",
|
||||||
|
field=models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowevent",
|
||||||
|
name="workflow_type",
|
||||||
|
field=models.CharField(blank=True, default="file_summary", max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
name="node_group",
|
||||||
|
field=models.CharField(blank=True, default="file_summary", max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
name="workflow_batch_id",
|
||||||
|
field=models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
name="workflow_type",
|
||||||
|
field=models.CharField(blank=True, default="file_summary", max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="export_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("markdown", "Markdown"),
|
||||||
|
("excel", "Excel"),
|
||||||
|
("json", "JSON"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="workflowevent",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="events",
|
||||||
|
to="review_agent.filesummarybatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="node_runs",
|
||||||
|
to="review_agent.filesummarybatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id"],
|
||||||
|
name="idx_ra_export_workflow",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="workflowevent",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id", "id"],
|
||||||
|
name="idx_ra_event_workflow_id",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id"],
|
||||||
|
name="idx_ra_node_workflow",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
name="conversation",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="regulatory_review_batches",
|
||||||
|
to="review_agent.conversation",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
name="source_summary_batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="regulatory_review_batches",
|
||||||
|
to="review_agent.filesummarybatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
name="trigger_message",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="triggered_regulatory_batches",
|
||||||
|
to="review_agent.message",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="review_regulatory_batches",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatorynotificationrecord",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notifications",
|
||||||
|
to="review_agent.regulatoryreviewbatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryissue",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="issues",
|
||||||
|
to="review_agent.regulatoryreviewbatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryartifact",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="artifacts",
|
||||||
|
to="review_agent.regulatoryreviewbatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryruleversion",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["code", "status"], name="idx_ra_rule_code_status"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
name="rule_version",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="review_batches",
|
||||||
|
to="review_agent.regulatoryruleversion",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatorynotificationrecord",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "status"], name="idx_ra_rr_notify_status"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryissue",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "severity"], name="idx_ra_rr_issue_severity"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryissue",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "category"], name="idx_ra_rr_issue_category"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryartifact",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "artifact_type"], name="idx_ra_rr_artifact_type"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["conversation", "created_at"], name="idx_ra_rr_batch_conv"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["user", "created_at"], name="idx_ra_rr_batch_user"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryreviewbatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["status", "created_at"], name="idx_ra_rr_batch_status"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -261,8 +261,13 @@ class WorkflowNodeRun(models.Model):
|
|||||||
batch = models.ForeignKey(
|
batch = models.ForeignKey(
|
||||||
FileSummaryBatch,
|
FileSummaryBatch,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
related_name="node_runs",
|
related_name="node_runs",
|
||||||
)
|
)
|
||||||
|
workflow_type = models.CharField(max_length=40, blank=True, default="file_summary")
|
||||||
|
workflow_batch_id = models.PositiveBigIntegerField(null=True, blank=True)
|
||||||
|
node_group = models.CharField(max_length=40, blank=True, default="file_summary")
|
||||||
node_code = models.CharField(max_length=40)
|
node_code = models.CharField(max_length=40)
|
||||||
node_name = models.CharField(max_length=80)
|
node_name = models.CharField(max_length=80)
|
||||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
||||||
@@ -278,6 +283,10 @@ class WorkflowNodeRun(models.Model):
|
|||||||
]
|
]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["batch", "status"], name="idx_ra_node_batch_status"),
|
models.Index(fields=["batch", "status"], name="idx_ra_node_batch_status"),
|
||||||
|
models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id"],
|
||||||
|
name="idx_ra_node_workflow",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -287,8 +296,19 @@ class WorkflowEvent(models.Model):
|
|||||||
batch = models.ForeignKey(
|
batch = models.ForeignKey(
|
||||||
FileSummaryBatch,
|
FileSummaryBatch,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
related_name="events",
|
related_name="events",
|
||||||
)
|
)
|
||||||
|
workflow_type = models.CharField(max_length=40, blank=True, default="file_summary")
|
||||||
|
workflow_batch_id = models.PositiveBigIntegerField(null=True, blank=True)
|
||||||
|
conversation = models.ForeignKey(
|
||||||
|
Conversation,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="workflow_events",
|
||||||
|
)
|
||||||
event_type = models.CharField(max_length=40)
|
event_type = models.CharField(max_length=40)
|
||||||
payload = models.JSONField(default=dict)
|
payload = models.JSONField(default=dict)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
@@ -299,6 +319,10 @@ class WorkflowEvent(models.Model):
|
|||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["batch", "id"], name="idx_ra_event_batch_id"),
|
models.Index(fields=["batch", "id"], name="idx_ra_event_batch_id"),
|
||||||
models.Index(fields=["batch", "created_at"], name="idx_ra_event_batch_created"),
|
models.Index(fields=["batch", "created_at"], name="idx_ra_event_batch_created"),
|
||||||
|
models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id", "id"],
|
||||||
|
name="idx_ra_event_workflow_id",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -308,6 +332,7 @@ class ExportedSummaryFile(models.Model):
|
|||||||
class ExportType(models.TextChoices):
|
class ExportType(models.TextChoices):
|
||||||
MARKDOWN = "markdown", "Markdown"
|
MARKDOWN = "markdown", "Markdown"
|
||||||
EXCEL = "excel", "Excel"
|
EXCEL = "excel", "Excel"
|
||||||
|
JSON = "json", "JSON"
|
||||||
|
|
||||||
class Status(models.TextChoices):
|
class Status(models.TextChoices):
|
||||||
SUCCESS = "success", "成功"
|
SUCCESS = "success", "成功"
|
||||||
@@ -318,6 +343,9 @@ class ExportedSummaryFile(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="exports",
|
related_name="exports",
|
||||||
)
|
)
|
||||||
|
workflow_type = models.CharField(max_length=40, blank=True, default="file_summary")
|
||||||
|
workflow_batch_id = models.PositiveBigIntegerField(null=True, blank=True)
|
||||||
|
export_category = models.CharField(max_length=40, blank=True, default="summary")
|
||||||
export_type = models.CharField(max_length=20, choices=ExportType.choices)
|
export_type = models.CharField(max_length=20, choices=ExportType.choices)
|
||||||
file_name = models.CharField(max_length=255)
|
file_name = models.CharField(max_length=255)
|
||||||
storage_path = models.CharField(max_length=500)
|
storage_path = models.CharField(max_length=500)
|
||||||
@@ -331,4 +359,210 @@ class ExportedSummaryFile(models.Model):
|
|||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["batch", "export_type"], name="idx_ra_export_batch_type"),
|
models.Index(fields=["batch", "export_type"], name="idx_ra_export_batch_type"),
|
||||||
models.Index(fields=["batch", "created_at"], name="idx_ra_export_batch_created"),
|
models.Index(fields=["batch", "created_at"], name="idx_ra_export_batch_created"),
|
||||||
|
models.Index(
|
||||||
|
fields=["workflow_type", "workflow_batch_id"],
|
||||||
|
name="idx_ra_export_workflow",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryRuleVersion(models.Model):
|
||||||
|
"""Tracks the local regulatory rule YAML and its matching RAG index."""
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
ACTIVE = "active", "启用"
|
||||||
|
OUTDATED = "outdated", "待更新"
|
||||||
|
DISABLED = "disabled", "停用"
|
||||||
|
|
||||||
|
code = models.CharField(max_length=80, unique=True)
|
||||||
|
name = models.CharField(max_length=160)
|
||||||
|
yaml_path = models.CharField(max_length=500)
|
||||||
|
yaml_hash = models.CharField(max_length=128)
|
||||||
|
rag_collection = models.CharField(max_length=120, blank=True, default="")
|
||||||
|
rag_index_version = models.CharField(max_length=80, blank=True, default="")
|
||||||
|
rag_index_hash = models.CharField(max_length=128, blank=True, default="")
|
||||||
|
status = models.CharField(max_length=20, choices=Status.choices, default=Status.ACTIVE)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_rule_version"
|
||||||
|
ordering = ["-updated_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["code", "status"], name="idx_ra_rule_code_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.code
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryReviewBatch(models.Model):
|
||||||
|
"""Tracks one NMPA regulatory review workflow run."""
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = "pending", "待执行"
|
||||||
|
RUNNING = "running", "执行中"
|
||||||
|
SUCCESS = "success", "成功"
|
||||||
|
FAILED = "failed", "失败"
|
||||||
|
|
||||||
|
conversation = models.ForeignKey(
|
||||||
|
Conversation,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="regulatory_review_batches",
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="review_regulatory_batches",
|
||||||
|
)
|
||||||
|
trigger_message = models.ForeignKey(
|
||||||
|
Message,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="triggered_regulatory_batches",
|
||||||
|
)
|
||||||
|
source_summary_batch = models.ForeignKey(
|
||||||
|
FileSummaryBatch,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name="regulatory_review_batches",
|
||||||
|
)
|
||||||
|
rule_version = models.ForeignKey(
|
||||||
|
RegulatoryRuleVersion,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="review_batches",
|
||||||
|
)
|
||||||
|
batch_no = models.CharField(max_length=64, unique=True)
|
||||||
|
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
||||||
|
risk_summary = models.JSONField(default=dict, blank=True)
|
||||||
|
work_dir = models.CharField(max_length=500, blank=True, default="")
|
||||||
|
error_message = models.TextField(blank=True, default="")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
started_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
finished_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_review_batch"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["conversation", "created_at"], name="idx_ra_rr_batch_conv"),
|
||||||
|
models.Index(fields=["user", "created_at"], name="idx_ra_rr_batch_user"),
|
||||||
|
models.Index(fields=["status", "created_at"], name="idx_ra_rr_batch_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.batch_no
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryIssue(models.Model):
|
||||||
|
"""Stores one regulatory finding after risk consolidation."""
|
||||||
|
|
||||||
|
class Severity(models.TextChoices):
|
||||||
|
BLOCKING = "blocking", "阻断项"
|
||||||
|
HIGH = "high", "高风险"
|
||||||
|
MEDIUM = "medium", "中风险"
|
||||||
|
LOW = "low", "低风险"
|
||||||
|
INFO = "info", "提示"
|
||||||
|
|
||||||
|
class Category(models.TextChoices):
|
||||||
|
COMPLETENESS = "completeness", "完整性"
|
||||||
|
STRUCTURE = "structure", "章节"
|
||||||
|
CONSISTENCY = "consistency", "一致性"
|
||||||
|
RAG = "rag", "法规依据"
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
OPEN = "open", "待处理"
|
||||||
|
RESOLVED = "resolved", "已整改"
|
||||||
|
ACCEPTED = "accepted", "已接受"
|
||||||
|
|
||||||
|
batch = models.ForeignKey(
|
||||||
|
RegulatoryReviewBatch,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="issues",
|
||||||
|
)
|
||||||
|
rule_code = models.CharField(max_length=120, blank=True, default="")
|
||||||
|
category = models.CharField(max_length=40, choices=Category.choices)
|
||||||
|
severity = models.CharField(max_length=20, choices=Severity.choices)
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
|
detail = models.TextField(blank=True, default="")
|
||||||
|
suggestion = models.TextField(blank=True, default="")
|
||||||
|
status = models.CharField(max_length=20, choices=Status.choices, default=Status.OPEN)
|
||||||
|
evidence = models.JSONField(default=dict, blank=True)
|
||||||
|
citations = models.JSONField(default=list, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_issue"
|
||||||
|
ordering = ["severity", "id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["batch", "severity"], name="idx_ra_rr_issue_severity"),
|
||||||
|
models.Index(fields=["batch", "category"], name="idx_ra_rr_issue_category"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryArtifact(models.Model):
|
||||||
|
"""Stores regulatory review intermediate and exported artifacts."""
|
||||||
|
|
||||||
|
class ArtifactType(models.TextChoices):
|
||||||
|
MARKDOWN = "markdown", "Markdown"
|
||||||
|
EXCEL = "excel", "Excel"
|
||||||
|
JSON = "json", "JSON"
|
||||||
|
TEXT = "text", "文本"
|
||||||
|
|
||||||
|
batch = models.ForeignKey(
|
||||||
|
RegulatoryReviewBatch,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="artifacts",
|
||||||
|
)
|
||||||
|
artifact_type = models.CharField(max_length=20, choices=ArtifactType.choices)
|
||||||
|
name = models.CharField(max_length=160)
|
||||||
|
storage_path = models.CharField(max_length=500)
|
||||||
|
content_hash = models.CharField(max_length=128, blank=True, default="")
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_artifact"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["batch", "artifact_type"], name="idx_ra_rr_artifact_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryNotificationRecord(models.Model):
|
||||||
|
"""Stores mock notification records for future Feishu integration."""
|
||||||
|
|
||||||
|
class Channel(models.TextChoices):
|
||||||
|
MOCK = "mock", "模拟"
|
||||||
|
FEISHU = "feishu", "飞书"
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = "pending", "待发送"
|
||||||
|
SENT = "sent", "已发送"
|
||||||
|
FAILED = "failed", "失败"
|
||||||
|
|
||||||
|
batch = models.ForeignKey(
|
||||||
|
RegulatoryReviewBatch,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="notifications",
|
||||||
|
)
|
||||||
|
channel = models.CharField(max_length=20, choices=Channel.choices, default=Channel.MOCK)
|
||||||
|
target = models.CharField(max_length=160, blank=True, default="")
|
||||||
|
payload = models.JSONField(default=dict, blank=True)
|
||||||
|
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
||||||
|
error_message = models.TextField(blank=True, default="")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
sent_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_notification_record"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["batch", "status"], name="idx_ra_rr_notify_status"),
|
||||||
]
|
]
|
||||||
|
|||||||
137
tests/test_regulatory_models.py
Normal file
137
tests/test_regulatory_models.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from review_agent.models import (
|
||||||
|
Conversation,
|
||||||
|
ExportedSummaryFile,
|
||||||
|
FileSummaryBatch,
|
||||||
|
Message,
|
||||||
|
RegulatoryArtifact,
|
||||||
|
RegulatoryIssue,
|
||||||
|
RegulatoryNotificationRecord,
|
||||||
|
RegulatoryReviewBatch,
|
||||||
|
RegulatoryRuleVersion,
|
||||||
|
WorkflowEvent,
|
||||||
|
WorkflowNodeRun,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_regulatory_models_store_batch_issue_artifact_and_notification(django_user_model):
|
||||||
|
user = django_user_model.objects.create_user(username="owner", password="pass")
|
||||||
|
conversation = Conversation.objects.create(user=user, title="法规核查")
|
||||||
|
trigger = Message.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
role=Message.Role.USER,
|
||||||
|
content="请做NMPA法规核查",
|
||||||
|
)
|
||||||
|
summary_batch = FileSummaryBatch.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
user=user,
|
||||||
|
batch_no="FS-READY",
|
||||||
|
status=FileSummaryBatch.Status.SUCCESS,
|
||||||
|
)
|
||||||
|
rule_version = RegulatoryRuleVersion.objects.create(
|
||||||
|
code="nmpa_ivd_registration_v1",
|
||||||
|
name="NMPA IVD 注册资料 Demo 规则",
|
||||||
|
yaml_path="review_agent/regulatory_review/rules/nmpa_ivd_registration_v1.yaml",
|
||||||
|
yaml_hash="abc123",
|
||||||
|
rag_collection="nmpa_ivd_registration_v1",
|
||||||
|
rag_index_version="idx-1",
|
||||||
|
rag_index_hash="hash-1",
|
||||||
|
status=RegulatoryRuleVersion.Status.ACTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
batch = RegulatoryReviewBatch.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
user=user,
|
||||||
|
trigger_message=trigger,
|
||||||
|
source_summary_batch=summary_batch,
|
||||||
|
rule_version=rule_version,
|
||||||
|
batch_no="RR-202606070001-abcdef",
|
||||||
|
)
|
||||||
|
issue = RegulatoryIssue.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
rule_code="registration_test_report",
|
||||||
|
category=RegulatoryIssue.Category.COMPLETENESS,
|
||||||
|
severity=RegulatoryIssue.Severity.BLOCKING,
|
||||||
|
title="缺少注册检验报告",
|
||||||
|
suggestion="请补充注册检验报告并复核。",
|
||||||
|
evidence={"matched_files": []},
|
||||||
|
citations=[{"source": "法规.doc", "text": "注册检验报告"}],
|
||||||
|
)
|
||||||
|
artifact = RegulatoryArtifact.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
artifact_type=RegulatoryArtifact.ArtifactType.JSON,
|
||||||
|
name="结果包",
|
||||||
|
storage_path="media/regulatory_review/result.json",
|
||||||
|
content_hash="hash",
|
||||||
|
)
|
||||||
|
notification = RegulatoryNotificationRecord.objects.create(
|
||||||
|
batch=batch,
|
||||||
|
channel=RegulatoryNotificationRecord.Channel.MOCK,
|
||||||
|
target="todo-plan",
|
||||||
|
payload={"issue_id": issue.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert batch.status == RegulatoryReviewBatch.Status.PENDING
|
||||||
|
assert batch.source_summary_batch == summary_batch
|
||||||
|
assert issue.status == RegulatoryIssue.Status.OPEN
|
||||||
|
assert artifact.artifact_type == RegulatoryArtifact.ArtifactType.JSON
|
||||||
|
assert notification.status == RegulatoryNotificationRecord.Status.PENDING
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic_workflow_fields_support_file_summary_and_regulatory_batches(django_user_model):
|
||||||
|
user = django_user_model.objects.create_user(username="owner", password="pass")
|
||||||
|
conversation = Conversation.objects.create(user=user, title="会话")
|
||||||
|
summary_batch = FileSummaryBatch.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
user=user,
|
||||||
|
batch_no="FS-GENERIC",
|
||||||
|
)
|
||||||
|
regulatory_batch = RegulatoryReviewBatch.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
user=user,
|
||||||
|
source_summary_batch=summary_batch,
|
||||||
|
batch_no="RR-GENERIC",
|
||||||
|
)
|
||||||
|
|
||||||
|
file_node = WorkflowNodeRun.objects.create(
|
||||||
|
batch=summary_batch,
|
||||||
|
workflow_type="file_summary",
|
||||||
|
workflow_batch_id=summary_batch.pk,
|
||||||
|
node_group="file_summary",
|
||||||
|
node_code="inventory",
|
||||||
|
node_name="文件扫描",
|
||||||
|
)
|
||||||
|
regulatory_node = WorkflowNodeRun.objects.create(
|
||||||
|
workflow_type="regulatory_review",
|
||||||
|
workflow_batch_id=regulatory_batch.pk,
|
||||||
|
node_group="regulatory_review",
|
||||||
|
node_code="prepare",
|
||||||
|
node_name="准备",
|
||||||
|
)
|
||||||
|
event = WorkflowEvent.objects.create(
|
||||||
|
batch=summary_batch,
|
||||||
|
workflow_type="regulatory_review",
|
||||||
|
workflow_batch_id=regulatory_batch.pk,
|
||||||
|
conversation=conversation,
|
||||||
|
event_type="workflow_created",
|
||||||
|
payload={"batch_no": regulatory_batch.batch_no},
|
||||||
|
)
|
||||||
|
exported = ExportedSummaryFile.objects.create(
|
||||||
|
batch=summary_batch,
|
||||||
|
workflow_type="regulatory_review",
|
||||||
|
workflow_batch_id=regulatory_batch.pk,
|
||||||
|
export_category="result_package",
|
||||||
|
export_type=ExportedSummaryFile.ExportType.JSON,
|
||||||
|
file_name="result.json",
|
||||||
|
storage_path="media/regulatory_review/result.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert file_node.batch == summary_batch
|
||||||
|
assert regulatory_node.batch is None
|
||||||
|
assert regulatory_node.workflow_batch_id == regulatory_batch.pk
|
||||||
|
assert event.conversation == conversation
|
||||||
|
assert exported.export_type == ExportedSummaryFile.ExportType.JSON
|
||||||
Reference in New Issue
Block a user