feat(regulatory-info-package): 增加材料包数据模型
This commit is contained in:
@@ -14,6 +14,7 @@ from review_agent.models import (
|
|||||||
ExportedSummaryFile,
|
ExportedSummaryFile,
|
||||||
FileAttachment,
|
FileAttachment,
|
||||||
Message,
|
Message,
|
||||||
|
RegulatoryInfoPackageBatch,
|
||||||
RegulatoryReviewBatch,
|
RegulatoryReviewBatch,
|
||||||
)
|
)
|
||||||
from review_agent.models import FileSummaryBatch, WorkflowEvent
|
from review_agent.models import FileSummaryBatch, WorkflowEvent
|
||||||
@@ -304,14 +305,20 @@ 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)
|
||||||
|
suffix = Path(exported.file_name).suffix.lower()
|
||||||
content_types = {
|
content_types = {
|
||||||
ExportedSummaryFile.ExportType.MARKDOWN: "text/markdown; charset=utf-8",
|
ExportedSummaryFile.ExportType.MARKDOWN: "text/markdown; charset=utf-8",
|
||||||
ExportedSummaryFile.ExportType.EXCEL: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
ExportedSummaryFile.ExportType.EXCEL: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
ExportedSummaryFile.ExportType.JSON: "application/json; charset=utf-8",
|
ExportedSummaryFile.ExportType.JSON: "application/json; charset=utf-8",
|
||||||
ExportedSummaryFile.ExportType.WORD: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
ExportedSummaryFile.ExportType.WORD: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
ExportedSummaryFile.ExportType.PDF: "application/pdf",
|
ExportedSummaryFile.ExportType.PDF: "application/pdf",
|
||||||
|
ExportedSummaryFile.ExportType.ZIP: "application/zip",
|
||||||
}
|
}
|
||||||
content_type = content_types.get(exported.export_type, "application/octet-stream")
|
content_type = content_types.get(exported.export_type, "application/octet-stream")
|
||||||
|
if exported.export_type == ExportedSummaryFile.ExportType.WORD and suffix == ".doc":
|
||||||
|
content_type = "application/msword"
|
||||||
|
elif exported.export_type == ExportedSummaryFile.ExportType.WORD and suffix == ".docx":
|
||||||
|
content_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
logger.info(
|
logger.info(
|
||||||
"Export download started",
|
"Export download started",
|
||||||
extra={
|
extra={
|
||||||
@@ -342,6 +349,17 @@ def _export_for_user(user, export_id: int) -> ExportedSummaryFile | None:
|
|||||||
is_deleted=False,
|
is_deleted=False,
|
||||||
).exists()
|
).exists()
|
||||||
return exported if allowed else None
|
return exported if allowed else None
|
||||||
|
if exported.workflow_type == "regulatory_info_package":
|
||||||
|
if not exported.workflow_batch_id:
|
||||||
|
return None
|
||||||
|
allowed = RegulatoryInfoPackageBatch.objects.filter(
|
||||||
|
pk=exported.workflow_batch_id,
|
||||||
|
conversation__user=user,
|
||||||
|
is_deleted=False,
|
||||||
|
).exists()
|
||||||
|
return exported if allowed else None
|
||||||
|
if exported.batch_id is None:
|
||||||
|
return None
|
||||||
if exported.batch.user_id != user.pk:
|
if exported.batch.user_id != user.pk:
|
||||||
return None
|
return None
|
||||||
return exported
|
return exported
|
||||||
|
|||||||
@@ -0,0 +1,388 @@
|
|||||||
|
# Generated by Django 5.2.14 on 2026-06-10 11:12
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("review_agent", "0008_knowledgebasedocument"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryInfoPackageArtifact",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"artifact_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("template_copy", "模板副本"),
|
||||||
|
("instruction_extract", "说明书抽取结果"),
|
||||||
|
("field_extract_result", "字段抽取结果"),
|
||||||
|
("merged_fields", "合并字段"),
|
||||||
|
("generated_document", "生成文件"),
|
||||||
|
("traceability", "追溯清单"),
|
||||||
|
("zip_package", "ZIP包"),
|
||||||
|
("notification_record", "通知记录"),
|
||||||
|
],
|
||||||
|
max_length=60,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file_format",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("json", "JSON"),
|
||||||
|
("excel", "Excel"),
|
||||||
|
("docx", "DOCX"),
|
||||||
|
("doc", "DOC"),
|
||||||
|
("zip", "ZIP"),
|
||||||
|
("markdown", "Markdown"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=160)),
|
||||||
|
("file_name", models.CharField(max_length=255)),
|
||||||
|
("storage_path", models.CharField(max_length=500)),
|
||||||
|
("file_size", models.BigIntegerField(default=0)),
|
||||||
|
(
|
||||||
|
"content_hash",
|
||||||
|
models.CharField(blank=True, default="", max_length=128),
|
||||||
|
),
|
||||||
|
("metadata", models.JSONField(blank=True, default=dict)),
|
||||||
|
(
|
||||||
|
"created_by_node",
|
||||||
|
models.CharField(blank=True, default="", max_length=60),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("is_deleted", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_info_package_artifact",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryInfoPackageBatch",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"source_summary_item_id",
|
||||||
|
models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
("batch_no", models.CharField(max_length=64, unique=True)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "待执行"),
|
||||||
|
("running", "执行中"),
|
||||||
|
("waiting_user", "等待用户"),
|
||||||
|
("success", "成功"),
|
||||||
|
("partial_success", "部分成功"),
|
||||||
|
("failed", "失败"),
|
||||||
|
("cancelled", "已取消"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"source_file_name",
|
||||||
|
models.CharField(blank=True, default="", max_length=255),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"source_storage_path",
|
||||||
|
models.CharField(blank=True, default="", max_length=500),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"product_name",
|
||||||
|
models.CharField(blank=True, default="", max_length=200),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"output_zip_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
default="第1章 监管信息(预生成版).zip",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("generated_files", models.JSONField(blank=True, default=list)),
|
||||||
|
("missing_fields", models.JSONField(blank=True, default=list)),
|
||||||
|
("llm_only_fields", models.JSONField(blank=True, default=list)),
|
||||||
|
("conflict_fields", models.JSONField(blank=True, default=list)),
|
||||||
|
("risk_notes", models.JSONField(blank=True, default=list)),
|
||||||
|
(
|
||||||
|
"template_config_version",
|
||||||
|
models.CharField(blank=True, default="", max_length=80),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"template_config_hash",
|
||||||
|
models.CharField(blank=True, default="", max_length=128),
|
||||||
|
),
|
||||||
|
("adapter_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)),
|
||||||
|
("archived_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
("is_deleted", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_info_package_batch",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RegulatoryInfoPackageNotificationRecord",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"channel",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("feishu_cli", "飞书 CLI"),
|
||||||
|
("feishu_api", "飞书 API"),
|
||||||
|
("mock", "模拟"),
|
||||||
|
],
|
||||||
|
default="mock",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("export_ids", models.JSONField(blank=True, default=list)),
|
||||||
|
("message_summary", models.TextField(blank=True, default="")),
|
||||||
|
(
|
||||||
|
"send_status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "待发送"),
|
||||||
|
("success", "成功"),
|
||||||
|
("failed", "失败"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("retry_count", models.PositiveIntegerField(default=0)),
|
||||||
|
(
|
||||||
|
"external_message_id",
|
||||||
|
models.CharField(blank=True, default="", max_length=120),
|
||||||
|
),
|
||||||
|
("error_message", models.TextField(blank=True, default="")),
|
||||||
|
("sent_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("is_deleted", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ra_regulatory_info_package_notification_record",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="exports",
|
||||||
|
to="review_agent.filesummarybatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="exportedsummaryfile",
|
||||||
|
name="export_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("markdown", "Markdown"),
|
||||||
|
("excel", "Excel"),
|
||||||
|
("json", "JSON"),
|
||||||
|
("word", "Word"),
|
||||||
|
("pdf", "PDF"),
|
||||||
|
("zip", "ZIP"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="workflownoderun",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("workflow_type", "workflow_batch_id", "node_code"),
|
||||||
|
name="uq_ra_node_workflow_batch_code",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
name="conversation",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
to="review_agent.conversation",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
name="source_attachment",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
to="review_agent.fileattachment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
name="source_summary_batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
to="review_agent.filesummarybatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
name="trigger_message",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="triggered_regulatory_info_package_batches",
|
||||||
|
to="review_agent.message",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="review_regulatory_info_package_batches",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackageartifact",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="artifacts",
|
||||||
|
to="review_agent.regulatoryinfopackagebatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagenotificationrecord",
|
||||||
|
name="batch",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notifications",
|
||||||
|
to="review_agent.regulatoryinfopackagebatch",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="regulatoryinfopackagenotificationrecord",
|
||||||
|
name="recipient",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="regulatory_info_package_notifications",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["conversation", "status"], name="idx_ra_rip_batch_conv_status"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["user", "created_at"], name="idx_ra_rip_batch_user_created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["source_attachment"], name="idx_ra_rip_batch_attachment"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["source_summary_batch"], name="idx_ra_rip_batch_summary"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagebatch",
|
||||||
|
index=models.Index(fields=["created_at"], name="idx_ra_rip_batch_created"),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackageartifact",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "artifact_type"], name="idx_ra_rip_artifact_batch_type"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackageartifact",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["file_format"], name="idx_ra_rip_artifact_format"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackageartifact",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["created_at"], name="idx_ra_rip_artifact_created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagenotificationrecord",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["batch", "created_at"], name="idx_ra_rip_notify_batch"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagenotificationrecord",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["recipient", "send_status"], name="idx_ra_rip_notify_recipient"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="regulatoryinfopackagenotificationrecord",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["send_status", "retry_count"], name="idx_ra_rip_notify_status"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -280,7 +280,11 @@ class WorkflowNodeRun(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
db_table = "ra_workflow_node_run"
|
db_table = "ra_workflow_node_run"
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=["batch", "node_code"], name="uq_ra_node_batch_code")
|
models.UniqueConstraint(fields=["batch", "node_code"], name="uq_ra_node_batch_code"),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["workflow_type", "workflow_batch_id", "node_code"],
|
||||||
|
name="uq_ra_node_workflow_batch_code",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["batch", "status"], name="idx_ra_node_batch_status"),
|
models.Index(fields=["batch", "status"], name="idx_ra_node_batch_status"),
|
||||||
@@ -336,6 +340,7 @@ class ExportedSummaryFile(models.Model):
|
|||||||
JSON = "json", "JSON"
|
JSON = "json", "JSON"
|
||||||
WORD = "word", "Word"
|
WORD = "word", "Word"
|
||||||
PDF = "pdf", "PDF"
|
PDF = "pdf", "PDF"
|
||||||
|
ZIP = "zip", "ZIP"
|
||||||
|
|
||||||
class Status(models.TextChoices):
|
class Status(models.TextChoices):
|
||||||
SUCCESS = "success", "成功"
|
SUCCESS = "success", "成功"
|
||||||
@@ -345,6 +350,8 @@ class ExportedSummaryFile(models.Model):
|
|||||||
FileSummaryBatch,
|
FileSummaryBatch,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="exports",
|
related_name="exports",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
workflow_type = models.CharField(max_length=40, blank=True, default="file_summary")
|
workflow_type = models.CharField(max_length=40, blank=True, default="file_summary")
|
||||||
workflow_batch_id = models.PositiveBigIntegerField(null=True, blank=True)
|
workflow_batch_id = models.PositiveBigIntegerField(null=True, blank=True)
|
||||||
@@ -524,6 +531,87 @@ class ApplicationFormFillBatch(models.Model):
|
|||||||
return self.batch_no
|
return self.batch_no
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryInfoPackageBatch(models.Model):
|
||||||
|
"""Tracks one Chapter 1 regulatory information package workflow run."""
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = "pending", "待执行"
|
||||||
|
RUNNING = "running", "执行中"
|
||||||
|
WAITING_USER = "waiting_user", "等待用户"
|
||||||
|
SUCCESS = "success", "成功"
|
||||||
|
PARTIAL_SUCCESS = "partial_success", "部分成功"
|
||||||
|
FAILED = "failed", "失败"
|
||||||
|
CANCELLED = "cancelled", "已取消"
|
||||||
|
|
||||||
|
conversation = models.ForeignKey(
|
||||||
|
Conversation,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="review_regulatory_info_package_batches",
|
||||||
|
)
|
||||||
|
trigger_message = models.ForeignKey(
|
||||||
|
Message,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="triggered_regulatory_info_package_batches",
|
||||||
|
)
|
||||||
|
source_attachment = models.ForeignKey(
|
||||||
|
FileAttachment,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
)
|
||||||
|
source_summary_batch = models.ForeignKey(
|
||||||
|
FileSummaryBatch,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="regulatory_info_package_batches",
|
||||||
|
)
|
||||||
|
source_summary_item_id = models.PositiveBigIntegerField(null=True, blank=True)
|
||||||
|
batch_no = models.CharField(max_length=64, unique=True)
|
||||||
|
status = models.CharField(max_length=30, choices=Status.choices, default=Status.PENDING)
|
||||||
|
source_file_name = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
source_storage_path = models.CharField(max_length=500, blank=True, default="")
|
||||||
|
product_name = models.CharField(max_length=200, blank=True, default="")
|
||||||
|
output_zip_name = models.CharField(max_length=255, blank=True, default="第1章 监管信息(预生成版).zip")
|
||||||
|
generated_files = models.JSONField(default=list, blank=True)
|
||||||
|
missing_fields = models.JSONField(default=list, blank=True)
|
||||||
|
llm_only_fields = models.JSONField(default=list, blank=True)
|
||||||
|
conflict_fields = models.JSONField(default=list, blank=True)
|
||||||
|
risk_notes = models.JSONField(default=list, blank=True)
|
||||||
|
template_config_version = models.CharField(max_length=80, blank=True, default="")
|
||||||
|
template_config_hash = models.CharField(max_length=128, blank=True, default="")
|
||||||
|
adapter_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)
|
||||||
|
archived_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
is_deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_info_package_batch"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["conversation", "status"], name="idx_ra_rip_batch_conv_status"),
|
||||||
|
models.Index(fields=["user", "created_at"], name="idx_ra_rip_batch_user_created"),
|
||||||
|
models.Index(fields=["source_attachment"], name="idx_ra_rip_batch_attachment"),
|
||||||
|
models.Index(fields=["source_summary_batch"], name="idx_ra_rip_batch_summary"),
|
||||||
|
models.Index(fields=["created_at"], name="idx_ra_rip_batch_created"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.batch_no
|
||||||
|
|
||||||
|
|
||||||
class RegulatoryReviewBatch(models.Model):
|
class RegulatoryReviewBatch(models.Model):
|
||||||
"""Tracks one NMPA regulatory review workflow run."""
|
"""Tracks one NMPA regulatory review workflow run."""
|
||||||
|
|
||||||
@@ -745,6 +833,54 @@ class ApplicationFormFillArtifact(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryInfoPackageArtifact(models.Model):
|
||||||
|
"""Stores regulatory information package intermediate and generated files."""
|
||||||
|
|
||||||
|
class ArtifactType(models.TextChoices):
|
||||||
|
TEMPLATE_COPY = "template_copy", "模板副本"
|
||||||
|
INSTRUCTION_EXTRACT = "instruction_extract", "说明书抽取结果"
|
||||||
|
FIELD_EXTRACT_RESULT = "field_extract_result", "字段抽取结果"
|
||||||
|
MERGED_FIELDS = "merged_fields", "合并字段"
|
||||||
|
GENERATED_DOCUMENT = "generated_document", "生成文件"
|
||||||
|
TRACEABILITY = "traceability", "追溯清单"
|
||||||
|
ZIP_PACKAGE = "zip_package", "ZIP包"
|
||||||
|
NOTIFICATION_RECORD = "notification_record", "通知记录"
|
||||||
|
|
||||||
|
class FileFormat(models.TextChoices):
|
||||||
|
JSON = "json", "JSON"
|
||||||
|
EXCEL = "excel", "Excel"
|
||||||
|
DOCX = "docx", "DOCX"
|
||||||
|
DOC = "doc", "DOC"
|
||||||
|
ZIP = "zip", "ZIP"
|
||||||
|
MARKDOWN = "markdown", "Markdown"
|
||||||
|
|
||||||
|
batch = models.ForeignKey(
|
||||||
|
RegulatoryInfoPackageBatch,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="artifacts",
|
||||||
|
)
|
||||||
|
artifact_type = models.CharField(max_length=60, choices=ArtifactType.choices)
|
||||||
|
file_format = models.CharField(max_length=20, choices=FileFormat.choices)
|
||||||
|
name = models.CharField(max_length=160)
|
||||||
|
file_name = models.CharField(max_length=255)
|
||||||
|
storage_path = models.CharField(max_length=500)
|
||||||
|
file_size = models.BigIntegerField(default=0)
|
||||||
|
content_hash = models.CharField(max_length=128, blank=True, default="")
|
||||||
|
metadata = models.JSONField(default=dict, blank=True)
|
||||||
|
created_by_node = models.CharField(max_length=60, blank=True, default="")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
is_deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_info_package_artifact"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["batch", "artifact_type"], name="idx_ra_rip_artifact_batch_type"),
|
||||||
|
models.Index(fields=["file_format"], name="idx_ra_rip_artifact_format"),
|
||||||
|
models.Index(fields=["created_at"], name="idx_ra_rip_artifact_created"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ApplicationFormFillNotificationRecord(models.Model):
|
class ApplicationFormFillNotificationRecord(models.Model):
|
||||||
"""Stores mock/Feishu notification records for application-form auto-fill."""
|
"""Stores mock/Feishu notification records for application-form auto-fill."""
|
||||||
|
|
||||||
@@ -795,6 +931,55 @@ class ApplicationFormFillNotificationRecord(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RegulatoryInfoPackageNotificationRecord(models.Model):
|
||||||
|
"""Stores mock/Feishu notification records for regulatory info packages."""
|
||||||
|
|
||||||
|
class Channel(models.TextChoices):
|
||||||
|
FEISHU_CLI = "feishu_cli", "飞书 CLI"
|
||||||
|
FEISHU_API = "feishu_api", "飞书 API"
|
||||||
|
MOCK = "mock", "模拟"
|
||||||
|
|
||||||
|
class SendStatus(models.TextChoices):
|
||||||
|
PENDING = "pending", "待发送"
|
||||||
|
SUCCESS = "success", "成功"
|
||||||
|
FAILED = "failed", "失败"
|
||||||
|
|
||||||
|
batch = models.ForeignKey(
|
||||||
|
RegulatoryInfoPackageBatch,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="notifications",
|
||||||
|
)
|
||||||
|
recipient = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="regulatory_info_package_notifications",
|
||||||
|
)
|
||||||
|
channel = models.CharField(max_length=30, choices=Channel.choices, default=Channel.MOCK)
|
||||||
|
export_ids = models.JSONField(default=list, blank=True)
|
||||||
|
message_summary = models.TextField(blank=True, default="")
|
||||||
|
send_status = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=SendStatus.choices,
|
||||||
|
default=SendStatus.PENDING,
|
||||||
|
)
|
||||||
|
retry_count = models.PositiveIntegerField(default=0)
|
||||||
|
external_message_id = models.CharField(max_length=120, blank=True, default="")
|
||||||
|
error_message = models.TextField(blank=True, default="")
|
||||||
|
sent_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
is_deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "ra_regulatory_info_package_notification_record"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["batch", "created_at"], name="idx_ra_rip_notify_batch"),
|
||||||
|
models.Index(fields=["recipient", "send_status"], name="idx_ra_rip_notify_recipient"),
|
||||||
|
models.Index(fields=["send_status", "retry_count"], name="idx_ra_rip_notify_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class FeishuUserMapping(models.Model):
|
class FeishuUserMapping(models.Model):
|
||||||
"""Maps a system user to Feishu identifiers maintained by Admin."""
|
"""Maps a system user to Feishu identifiers maintained by Admin."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user