File size: 7,629 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""
文书生成任务和配置模型

本模块定义文书生成相关的数据模型.
"""

from __future__ import annotations

import logging
from typing import Any, ClassVar

from django.db import models
from django.utils.translation import gettext_lazy as _

logger = logging.getLogger("apps.documents")


class GenerationMethod(models.TextChoices):
    """生成方式"""

    TEMPLATE = "template", _("模板生成")
    AI = "ai", _("AI生成")


class GenerationStatus(models.TextChoices):
    """生成状态"""

    PENDING = "pending", _("等待中")
    PROCESSING = "processing", _("生成中")
    COMPLETED = "completed", _("已完成")
    FAILED = "failed", _("失败")


class GenerationTask(models.Model):
    """
    文书生成任务

    记录每次文书生成的任务信息,包括生成参数、结果等.
    支持模板生成和 AI 生成两种方式.

    Requirements: 6.1.3
    """

    id: int
    case_id: int  # 外键ID字段
    contract_id: int  # 外键ID字段
    litigation_session_id: int  # 外键ID字段
    created_by_id: int  # 外键ID字段
    case = models.ForeignKey(
        "cases.Case",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="generation_tasks",
        verbose_name=_("关联案件"),
    )
    contract = models.ForeignKey(
        "contracts.Contract",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="generation_tasks",
        verbose_name=_("关联合同"),
    )
    litigation_session = models.ForeignKey(
        "litigation_ai.LitigationSession",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="generation_tasks",
        verbose_name=_("AI生成会话"),
        help_text=_("如果是 AI 生成,关联到对应的会话"),
    )
    generation_method = models.CharField(
        max_length=20, choices=GenerationMethod.choices, default=GenerationMethod.TEMPLATE, verbose_name=_("生成方式")
    )
    document_type = models.CharField(
        max_length=100, verbose_name=_("文书类型"), help_text=_("如:起诉状、答辩状、合同等")
    )
    template_id = models.IntegerField(
        null=True, blank=True, verbose_name=_("模板ID"), help_text=_("使用的模板ID(模板生成时)")
    )
    status = models.CharField(
        max_length=20, choices=GenerationStatus.choices, default=GenerationStatus.PENDING, verbose_name=_("生成状态")
    )
    result_file = models.FileField(
        upload_to="generated_documents/%Y/%m/", null=True, blank=True, verbose_name=_("生成文件")
    )
    error_message = models.TextField(blank=True, verbose_name=_("错误信息"))
    metadata: Any = models.JSONField(
        default=dict, verbose_name=_("任务元数据"), help_text=_("存储生成参数、token消耗等信息")
    )
    created_by = models.ForeignKey(
        "organization.Lawyer",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="created_generation_tasks",
        verbose_name=_("创建人"),
    )
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
    completed_at = models.DateTimeField(null=True, blank=True, verbose_name=_("完成时间"))

    class Meta:
        app_label = "documents"
        verbose_name = _("文书生成任务")
        verbose_name_plural = _("文书生成任务")
        ordering: ClassVar = ["-created_at"]
        indexes: ClassVar = [
            models.Index(fields=["case", "-created_at"]),
            models.Index(fields=["contract", "-created_at"]),
            models.Index(fields=["litigation_session"]),
            models.Index(fields=["status"]),
            models.Index(fields=["generation_method"]),
            models.Index(fields=["created_by", "-created_at"]),
        ]

    def __str__(self) -> str:
        resource = self.case or self.contract
        resource_name = resource.name if resource else "未关联"
        return f"{resource_name} - {self.document_type} ({self.get_status_display()})"

    @property
    def is_ai_generated(self) -> bool:
        """是否为 AI 生成"""
        return self.generation_method == GenerationMethod.AI

    @property
    def duration_seconds(self) -> int:
        """生成耗时(秒)"""
        if self.completed_at and self.created_at:
            return int((self.completed_at - self.created_at).total_seconds())
        return 0

    @property
    def folder_template_id(self) -> int | None:
        return (self.metadata or {}).get("folder_template_id")

    @folder_template_id.setter
    def folder_template_id(self, value: int | None) -> None:
        self.metadata = self.metadata or {}
        self.metadata["folder_template_id"] = value

    @property
    def output_path(self) -> str | None:
        return (self.metadata or {}).get("output_path")

    @output_path.setter
    def output_path(self, value: str | None) -> None:
        self.metadata = self.metadata or {}
        self.metadata["output_path"] = value

    @property
    def generated_files(self) -> list[Any]:
        return list((self.metadata or {}).get("generated_files", []))

    @generated_files.setter
    def generated_files(self, value: list[Any] | None) -> None:
        self.metadata = self.metadata or {}
        self.metadata["generated_files"] = list(value or [])

    @property
    def error_logs(self) -> list[Any]:
        return list((self.metadata or {}).get("error_logs", []))

    @error_logs.setter
    def error_logs(self, value: list[Any] | None) -> None:
        self.metadata = self.metadata or {}
        self.metadata["error_logs"] = list(value or [])


class GenerationConfig(models.Model):
    """
    文书生成配置

    存储文书生成的全局配置,如默认模板、生成参数等.

    Requirements: 6.1.3
    """

    id: int
    name = models.CharField(max_length=100, unique=True, verbose_name=_("配置名称"))
    config_type = models.CharField(
        max_length=50, verbose_name=_("配置类型"), help_text=_("如:default_template、ai_model、generation_params")
    )
    value: Any = models.JSONField(verbose_name=_("配置值"))
    description = models.TextField(blank=True, verbose_name=_("配置说明"))
    is_active = models.BooleanField(default=True, verbose_name=_("是否启用"))
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_("更新时间"))

    class Meta:
        app_label = "documents"
        verbose_name = _("文书生成配置")
        verbose_name_plural = _("文书生成配置")
        ordering: ClassVar = ["config_type", "name"]
        indexes: ClassVar = [
            models.Index(fields=["config_type"]),
            models.Index(fields=["is_active"]),
        ]

    def __str__(self) -> str:
        return f"{self.config_type} - {self.name}"

    @property
    def case_type(self) -> str | None:
        return (self.value or {}).get("case_type")

    @property
    def case_stage(self) -> str | None:
        return (self.value or {}).get("case_stage")

    @property
    def document_template_id(self) -> int | None:
        return (self.value or {}).get("document_template_id")

    @property
    def folder_path(self) -> str | None:
        return (self.value or {}).get("folder_path")

    @property
    def priority(self) -> int:
        return int((self.value or {}).get("priority", 0))

    @property
    def condition(self) -> dict[str, Any]:
        return (self.value or {}).get("condition") or {}