File size: 7,525 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | from __future__ import annotations
import uuid
from typing import ClassVar
from django.conf import settings
from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
class PdfSplitJobStatus(models.TextChoices):
PENDING = "pending", _("待处理")
PROCESSING = "processing", _("处理中")
REVIEW_REQUIRED = "review_required", _("待复核")
EXPORTING = "exporting", _("导出中")
COMPLETED = "completed", _("已完成")
FAILED = "failed", _("失败")
CANCELLED = "cancelled", _("已取消")
class PdfSplitSourceType(models.TextChoices):
UPLOAD = "upload", _("上传文件")
LOCAL_PATH = "local_path", _("本地路径")
class PdfSplitMode(models.TextChoices):
CONTENT_ANALYSIS = "content_analysis", _("内容识别拆分")
PAGE_SPLIT = "page_split", _("按页拆分")
MANUAL_SPLIT = "manual_split", _("手动拆分")
class PdfSplitOcrProfile(models.TextChoices):
FAST = "fast", _("快速")
BALANCED = "balanced", _("均衡")
ACCURATE = "accurate", _("高精度")
class PdfSplitSegmentType(models.TextChoices):
COMPLAINT = "complaint", _("起诉状")
EVIDENCE_LIST = "evidence_list", _("证据清单及明细")
PRESERVATION_MATERIALS = "preservation_materials", _("财产保全资料")
PARTY_IDENTITY = "party_identity", _("双方当事人主体信息")
AUTHORIZATION_MATERIALS = "authorization_materials", _("授权委托材料")
DELIVERY_ADDRESS_CONFIRMATION = "delivery_address_confirmation", _("送达地址确认书")
REFUND_ACCOUNT_CONFIRMATION = "refund_account_confirmation", _("诉讼费用退费账户确认书")
UNRECOGNIZED = "unrecognized", _("未识别材料")
class PdfSplitReviewFlag(models.TextChoices):
NORMAL = "normal", _("正常")
LOW_CONFIDENCE = "low_confidence", _("低置信度")
UNRECOGNIZED = "unrecognized", _("未识别")
OCR_FAILED = "ocr_failed", _("OCR失败")
class PdfSplittingTool(models.Model):
name = models.CharField(max_length=64, default="Pdf Splitting")
class Meta:
managed = False
verbose_name = _("PDF 拆解")
verbose_name_plural = _("PDF 拆解")
class PdfSplitJob(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
source_type = models.CharField(
max_length=16,
choices=PdfSplitSourceType.choices,
verbose_name=_("来源类型"),
)
source_abs_path = models.CharField(max_length=2048, blank=True, default="", verbose_name=_("源绝对路径"))
source_relpath = models.CharField(max_length=1024, blank=True, default="", verbose_name=_("源相对路径"))
source_original_name = models.CharField(max_length=255, verbose_name=_("源文件名"))
split_mode = models.CharField(
max_length=32,
choices=PdfSplitMode.choices,
default=PdfSplitMode.CONTENT_ANALYSIS,
verbose_name=_("拆分模式"),
)
template_key = models.CharField(max_length=64, default="filing_materials_v1", verbose_name=_("模板键"))
template_version = models.CharField(max_length=32, default="1", verbose_name=_("模板版本"))
ocr_profile = models.CharField(
max_length=16,
choices=PdfSplitOcrProfile.choices,
default=PdfSplitOcrProfile.BALANCED,
verbose_name=_("OCR 档位"),
)
status = models.CharField(
max_length=32,
choices=PdfSplitJobStatus.choices,
default=PdfSplitJobStatus.PENDING,
verbose_name=_("状态"),
)
total_pages = models.PositiveIntegerField(default=0, verbose_name=_("总页数"))
processed_pages = models.PositiveIntegerField(default=0, verbose_name=_("已处理页数"))
progress = models.PositiveIntegerField(default=0, verbose_name=_("进度"))
current_page = models.PositiveIntegerField(default=0, verbose_name=_("当前页"))
task_id = models.CharField(max_length=64, blank=True, default="", verbose_name=_("任务ID"))
cancel_requested = models.BooleanField(default=False, verbose_name=_("请求取消"))
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="pdf_split_jobs",
verbose_name=_("创建人"),
)
summary_payload = models.JSONField(default=dict, blank=True, verbose_name=_("摘要"))
error_message = models.TextField(blank=True, default="", verbose_name=_("错误信息"))
export_zip_relpath = models.CharField(max_length=1024, blank=True, default="", verbose_name=_("导出ZIP路径"))
started_at = models.DateTimeField(null=True, blank=True, verbose_name=_("开始时间"))
finished_at = models.DateTimeField(null=True, blank=True, verbose_name=_("完成时间"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("更新时间"))
class Meta:
verbose_name = _("PDF 拆解任务")
verbose_name_plural = _("PDF 拆解任务")
ordering: ClassVar[list[str]] = ["-created_at"]
indexes: ClassVar[list[models.Index]] = [
models.Index(fields=["status", "-created_at"]),
models.Index(fields=["created_by", "-created_at"]),
]
def __str__(self) -> str:
return f"{self.source_original_name} ({self.get_status_display()})"
class PdfSplitSegment(models.Model):
job = models.ForeignKey(
PdfSplitJob,
on_delete=models.CASCADE,
related_name="segments",
verbose_name=_("任务"),
)
order = models.PositiveIntegerField(default=1, verbose_name=_("排序"))
page_start = models.PositiveIntegerField(verbose_name=_("起始页"))
page_end = models.PositiveIntegerField(verbose_name=_("结束页"))
segment_type = models.CharField(
max_length=48,
choices=PdfSplitSegmentType.choices,
default=PdfSplitSegmentType.UNRECOGNIZED,
verbose_name=_("段类型"),
)
filename = models.CharField(max_length=255, verbose_name=_("文件名"))
confidence = models.FloatField(default=0.0, verbose_name=_("置信度"))
source_method = models.CharField(max_length=32, default="rule", verbose_name=_("来源方法"))
review_flag = models.CharField(
max_length=32,
choices=PdfSplitReviewFlag.choices,
default=PdfSplitReviewFlag.NORMAL,
verbose_name=_("复核标记"),
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("更新时间"))
class Meta:
verbose_name = _("PDF 拆解片段")
verbose_name_plural = _("PDF 拆解片段")
ordering: ClassVar[list[str]] = ["order", "id"]
constraints: ClassVar[list[models.BaseConstraint]] = [
models.UniqueConstraint(fields=["job", "order"], name="pdf_splitting_segment_job_order_uniq"),
]
def __str__(self) -> str:
return f"{self.job_id}:{self.page_start}-{self.page_end} {self.segment_type}"
@receiver(post_delete, sender=PdfSplitJob)
def delete_job_files(sender: type, instance: PdfSplitJob, **kwargs: object) -> None:
"""删除任务时清理关联的文件目录"""
from apps.pdf_splitting.services.storage import PdfSplitStorage
storage = PdfSplitStorage(instance.id)
storage.cleanup()
|