From adc1e0b8dc9364eb503160d525f763d1378fceb8 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Fri, 15 May 2026 14:15:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=A4=E5=8F=89=E8=AF=84=E6=9F=A5?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BC=98=E5=8C=96=20=E2=80=94=20=E8=AF=84?= =?UTF-8?q?=E6=9F=A5=E5=9C=B0=E5=8C=BA=E3=80=81=E6=96=87=E6=A1=A3=E8=AF=84?= =?UTF-8?q?=E6=9F=A5=E7=BB=9F=E8=AE=A1=E3=80=81currentScore=E3=80=81?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GetUserTasks: 新增 task_regions CTE,从任务成员 sso_users.area 去重收集 evaluationRegion - GetTaskDocuments: 新增 es LATERAL 子查询聚合 leaudit_rule_results 的 pass_count/warning_count/error_count/score_percent;path/uploadTime 改为从 leaudit_document_files 获取;新增 fileExt - ReviewPointResultVO: 新增 currentScore 字段 - _loadReviewPointResults: SQL 新增 approved_delta LATERAL 子查询,currentScore = base_score + SUM(approved_deltas) - CrossReviewTaskItemVO: 新增 evaluationRegion - CrossReviewTaskDocumentVO: 新增 18 个评查统计字段 + path/uploadTime/fileExt - 文档更新:交叉评查核心模块业务逻辑文档补充评查地区、评查统计、版本号本地化等章节 --- .deepseek/instructions.md | 136 ++++++++++++++++++ .../交叉评查核心模块业务逻辑与Tab实现计划.md | 114 ++++++++++++++- .../domian/vo/crossReviewVo.py | 18 +++ .../domian/vo/reviewPointVo.py | 1 + .../services/impl/crossReviewServiceImpl.py | 119 ++++++++++++++- .../services/impl/documentServiceImpl.py | 15 +- legal-platform-frontend | 1 + 7 files changed, 393 insertions(+), 11 deletions(-) create mode 100644 .deepseek/instructions.md create mode 160000 legal-platform-frontend diff --git a/.deepseek/instructions.md b/.deepseek/instructions.md new file mode 100644 index 0000000..b765944 --- /dev/null +++ b/.deepseek/instructions.md @@ -0,0 +1,136 @@ +# Project Structure (Auto-generated) + +> This file was automatically generated by DeepSeek TUI. +> You can edit or delete it at any time. + +**Summary:** A JavaScript/Node.js project + +**Tree:** +``` +DIR: .claude + DIR: skills +DIR: .git + FILE: COMMIT_EDITMSG + FILE: FETCH_HEAD + FILE: HEAD + FILE: ORIG_HEAD + DIR: branches + FILE: config + FILE: description + DIR: gk + DIR: hooks + FILE: index + DIR: info + DIR: objects + DIR: refs + DIR: worktrees +FILE: .gitignore +DIR: deploy + DIR: collabora-proxy +DIR: docs + DIR: Collabora + FILE: HANDOFF.md + DIR: RAG + FILE: README.md + DIR: leaudit + DIR: 交叉评查 + DIR: 入口模块 + DIR: 内部公文模块 + DIR: 文档管理 + DIR: 权限与地区隔离 + DIR: 系统使用统计 + DIR: 规则编辑 + DIR: 评查点分组 + DIR: 迁移与兼容 + DIR: 项目总览 +DIR: fastapi_admin + FILE: __init__.py + FILE: app.py + DIR: bootstrap_parts + FILE: celery_app.py + DIR: config +DIR: fastapi_common + FILE: __init__.py + DIR: fastapi_common_logger + DIR: fastapi_common_security + DIR: fastapi_common_sqlalchemy + DIR: fastapi_common_storage + DIR: fastapi_common_utils + DIR: fastapi_common_web +DIR: fastapi_modules + FILE: __init__.py + DIR: fastapi_leaudit +FILE: leaudit.sh +DIR: legal-platform-frontend + DIR: .codex-run + FILE: .env.example + DIR: .git + FILE: .gitignore + FILE: README.md + DIR: app + FILE: bun.lock + DIR: components + DIR: config + DIR: constants + DIR: contexts + DIR: docs + FILE: ecosystem.config.cjs + FILE: eslint.config.mjs + DIR: hooks + DIR: lib + FILE: middleware.ts + DIR: models + FILE: next.config.ts + FILE: orval.config.ts + FILE: package-lock.json + FILE: package.json + FILE: postcss.config.mjs + DIR: public + DIR: scripts + DIR: styles + FILE: tailwind.config.ts + DIR: tests + FILE: tsconfig.json + DIR: types + DIR: utils +DIR: new-rules + FILE: contract.lease.yaml + FILE: contract.sale.yaml + FILE: contract.tech.yaml +FILE: package.json +FILE: pyproject.toml +DIR: resources + FILE: .gitkeep +DIR: rules + DIR: contract_construction + DIR: contract_entrust + DIR: contract_evaluation + DIR: contract_gift_charity + DIR: contract_gift_general + DIR: contract_lease + DIR: contract_loan + DIR: contract_purchase + DIR: contract_sale + FILE: contract_sale.zip + DIR: contract_tech + DIR: 行政处罚 + DIR: 行政许可_停业 + DIR: 行政许可_变更 + DIR: 行政许可_延续 + DIR: 行政许可_恢复营业 + DIR: 行政许可_收回 + DIR: 行政许可_新办 + DIR: 行政许可_歇业 + DIR: 行政许可_注销 + DIR: 行政许可_补办 +FILE: run.py +DIR: scripts + FILE: dev.sh + FILE: m4_seed_rules.py + FILE: merge_document_version_groups.py + FILE: migrate_legacy_users.py + FILE: preview_document_version_merge.py + FILE: start_beat.sh + FILE: start_worker.sh + DIR: 创建sql +``` \ No newline at end of file diff --git a/docs/交叉评查/交叉评查核心模块业务逻辑与Tab实现计划.md b/docs/交叉评查/交叉评查核心模块业务逻辑与Tab实现计划.md index 5ddb4fb..3373193 100644 --- a/docs/交叉评查/交叉评查核心模块业务逻辑与Tab实现计划.md +++ b/docs/交叉评查/交叉评查核心模块业务逻辑与Tab实现计划.md @@ -49,11 +49,13 @@ |----|------|------| | `id` | INT PK | | | `task_id` | INT FK → tasks | 所属任务 | -| `document_id` | INT | 关联的文档 ID | -| `audit_status` | INT | 评查状态:`0`=未完成,`1`=已完成 | +| `document_id` | INT | 关联的文档 ID(FK → `leaudit_documents.id`) | +| `audit_status` | INT | 评查状态:`0`=未评查,`1`=已评查(**文档×任务级别**,非按人) | | `create_time` | TIMESTAMPTZ | | | `delete_time` | TIMESTAMPTZ | | +> **评查状态逻辑**:`CompleteTaskDocument` 将 `audit_status` 置为 1。当任务内全部文档的 `audit_status=1` 时,任务状态自动更新为 `completed`。 + ### 1.4 `leaudit_cross_review_proposals` — 交叉评查提案(意见) | 列 | 类型 | 说明 | @@ -83,6 +85,25 @@ | `create_time` | TIMESTAMPTZ | | | `delete_time` | TIMESTAMPTZ | | +### 1.6 依赖的外部表 + +任务列表和文档列表的查询依赖以下外部表(非交叉评查专属): + +| 表 | 用途 | +|----|------| +| `sso_users` | 用户信息;`area` 字段用于收集任务成员的评查地区 | +| `leaudit_documents` | 文档主表;含 `version_no`(全局版本号)、`version_group_key`、`current_run_id` | +| `leaudit_document_files` | 文档文件;第一个文件的 `local_path` 和 `created_at` 作为文档路径和上传时间 | +| `leaudit_rule_results` | 评查规则结果;聚合 `passed`/`risk`/`score`/`fail_message` 得出评查统计 | +| `leaudit_audit_runs` | 评查运行记录;通过 `d.current_run_id` 关联 | + +> **评查地区数据来源**:`GetUserTasks` 中 `task_regions` CTE 从 `leaudit_cross_review_task_members` JOIN `sso_users` 收集 `DISTINCT u.area`(排除 NULL 和空串),非从文档表获取。 + +> **评查统计数据来源**:`GetTaskDocuments` 中 `es` LATERAL 子查询聚合 `leaudit_rule_results`,按 `risk` 分级: +> - `passed IS TRUE` → pass_count +> - `passed IS FALSE AND risk = 'high'` → error_count +> - `passed IS FALSE AND risk IN ('low','medium')` → warning_count + --- ## 2. 后端 API 全景 @@ -173,13 +194,45 @@ pendingProposals: list[PendingProposalVO] # PendingProposalVO: { evaluationPointName, pendingVotersNum } ``` +**`CrossReviewTaskItemVO`** — 任务列表项: +```python +taskId: int +taskName: str +taskType: str +docTypeId: int | None +docTypeCode: str | None +status: str +progress: float +totalDocuments: int +completedDocuments: int +createdAt: datetime | None +evaluationRegion: list[str] # 评查地区 — 从任务成员 sso_users.area 去重收集 +``` + **`CrossReviewTaskDocumentVO`** — 任务文档列表项: ```python documentId, name, documentNumber, typeId, typeName processingStatus, versionNo, isLatestVersion versionGroupKey, totalVersions -auditStatus # 0=未完成, 1=已完成 +auditStatus # 0=未评查, 1=已评查(文档×任务级别) createdAt, fileSize +path # 文件路径 — 来自 leaudit_document_files.local_path +uploadTime # 上传时间 — 来自 leaudit_document_files.created_at +# 以下为评查统计字段 — 聚合自 leaudit_rule_results +totalEvaluationPoints: int # 总评查点数 +passCount: int # 通过数(passed IS TRUE) +warningCount: int # 警告数(passed IS FALSE AND risk IN ('low','medium')) +errorCount: int # 错误数(passed IS FALSE AND risk = 'high') +manualCount: int # 人工审核数(暂为 0) +issueCount: int # 问题总数 +warningMessages: list[str] # 警告消息 +errorMessages: list[str] # 错误消息 +issueMessages: list[str] # 问题消息(综合) +manualMessages: list[str] # 人工审核消息(暂为空) +finalScore: float # 最终得分(通过规则分数之和) +fullScore: float # 满分(所有规则分数之和) +scoreSummary: str # 得分摘要(如 "85.0/100.0") +scorePercent: float # 得分百分比(0-100) ``` --- @@ -795,7 +848,7 @@ import { CrossCheckingOpinionsPanel } from "@/components/cross-checking/CrossChe ## 7. 前端 API 封装参考 -全部位于 `lib/api/legacy/cross-checking/cross-file-result.ts`: +### 7.1 交叉意见相关(`cross-file-result.ts`) | 函数 | HTTP | 路径 | 用途 | |------|------|------|------| @@ -807,6 +860,45 @@ import { CrossCheckingOpinionsPanel } from "@/components/cross-checking/CrossChe | `updateCrossCheckingReviewResult` | PATCH | `/api/v3/review-points/{id}/audit` | 更新评查结果状态 | | `findIsProposer` | GET | `/api/v3/cross-review/tasks/{taskId}/can-confirm` | 检查是否负责人 | +### 7.2 任务列表与文档列表(`cross-files.ts`) + +| 函数 | HTTP | 路径 | 用途 | +|------|------|------|------| +| `getUserTaskDocuments` | POST | `/api/v3/cross-review/tasks/query` | 获取用户任务列表(V3) | +| `getTaskDocumentsWithVersions` | GET | `/api/v3/cross-review/tasks/{id}/documents` | 获取任务文档列表(含版本归纳) | +| `getCrossCheckingDocumentTypes` | — | — | 获取可选文档类型 | +| `getCrossCheckingTasks` | — | — | 任务列表包装(含前端筛选) | + +### 7.3 版本号任务本地化 + +`d.version_no` 是文档的**全局**版本号(跨所有任务),前端在 `getTaskDocumentsWithVersions` 中做任务本地重映射: + +``` +同一 version_group_key 内: +1. 按原始 version_number 升序排列(保留全局先后顺序) +2. 重新分配 version_number = 1, 2, 3... +3. 同时更新 history_versions 中的 version_number +``` + +**示例**:文档全局版本 v11 → 任务内第 1 次上传 → 显示 V1;v12 → 第 2 次 → 显示 V2。 + +### 7.4 评查统计数据映射 + +前端 `getTaskDocumentsWithVersions` 将后端 V3 返回的 camelCase 字段映射为内部使用的 snake_case: + +``` +API 返回 (JSON) → 前端类型 (CrossReviewDocumentWithVersion) +──────────────────────────────────────────────────────────── +passCount → pass_count +warningCount → warning_count +errorCount → error_count +manualCount → manual_count +scorePercent → score_percent +warningMessages → warning_messages +errorMessages → error_messages +... +``` + --- ## 8. 补充:后端服务实现详解 @@ -825,7 +917,12 @@ class CrossReviewServiceImpl(ICrossReviewService): async def GetUserTasks(self, CurrentUserId: int, dto: CrossReviewTaskQueryDTO) -> CrossReviewTaskPageVO: - """分页查询当前用户参与的任务""" + """ + 分页查询当前用户参与的任务。 + SQL 含两个 CTE: + - doc_stats: 统计每任务 total_documents / completed_documents + - task_regions: 从任务成员 JOIN sso_users 收集去重 area AS evaluationRegion + """ async def GetTaskProgress(self, CurrentUserId: int, TaskId: int) -> CrossReviewTaskProgressVO: @@ -834,7 +931,12 @@ class CrossReviewServiceImpl(ICrossReviewService): async def GetTaskDocuments(self, CurrentUserId: int, TaskId: int, dto: CrossReviewTaskDocumentQueryDTO) -> CrossReviewTaskDocumentPageVO: - """分页查询任务下的文档列表""" + """ + 分页查询任务下的文档列表。 + 含版本归纳:同一 version_group_key 的文档归为一组,totalVersions 为任务内计数。 + 含评查统计 LATERAL 子查询 (es):聚合 leaudit_rule_results 的 + pass_count / warning_count / error_count / score_percent 等。 + """ async def CanConfirmTaskDocument(self, CurrentUserId: int, TaskId: int) -> CrossReviewPermissionVO: diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py index 4e1c7b1..ed96061 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py @@ -20,6 +20,7 @@ class CrossReviewTaskItemVO(BaseModel): totalDocuments: int = Field(0, description="文档总数") completedDocuments: int = Field(0, description="已完成文档数") createdAt: datetime | None = Field(None, description="创建时间") + evaluationRegion: list[str] = Field(default_factory=list, description="评查地区") class CrossReviewTaskPageVO(BaseModel): @@ -56,6 +57,23 @@ class CrossReviewTaskDocumentVO(BaseModel): auditStatus: int = Field(0, description="任务内完成状态") createdAt: datetime | None = Field(None, description="创建时间") fileSize: int = Field(0, description="文件大小(字节)") + path: str | None = Field(None, description="文件存储路径") + uploadTime: datetime | None = Field(None, description="上传时间") + fileExt: str | None = Field(None, description="文件扩展名") + totalEvaluationPoints: int = Field(0, description="总评查点数") + passCount: int = Field(0, description="通过数") + warningCount: int = Field(0, description="警告数") + errorCount: int = Field(0, description="错误数") + manualCount: int = Field(0, description="人工审核数") + issueCount: int = Field(0, description="问题总数") + warningMessages: list[str] = Field(default_factory=list, description="警告消息") + errorMessages: list[str] = Field(default_factory=list, description="错误消息") + issueMessages: list[str] = Field(default_factory=list, description="问题消息") + manualMessages: list[str] = Field(default_factory=list, description="人工审核消息") + finalScore: float = Field(0, description="最终得分") + fullScore: float = Field(0, description="满分") + scoreSummary: str = Field("", description="得分摘要") + scorePercent: float = Field(0, description="得分百分比") class CrossReviewTaskDocumentPageVO(BaseModel): diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/reviewPointVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/reviewPointVo.py index 4ab7af4..964eddd 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/reviewPointVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/reviewPointVo.py @@ -31,6 +31,7 @@ class ReviewPointResultVO(BaseModel): score: float = Field(0, description="分值") finalScore: float | None = Field(None, description="最终得分") machineScore: float | None = Field(None, description="机器得分") + currentScore: float = Field(0, description="当前得分(含提案扣分/加分)") result: bool | None = Field(None, description="是否通过") failMessage: str = Field("", description="失败提示") passMessage: str = Field("", description="通过提示") diff --git a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py index 5448ba2..e2175eb 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py @@ -303,6 +303,17 @@ class CrossReviewServiceImpl(ICrossReviewService): FROM leaudit_cross_review_task_documents td WHERE td.delete_time IS NULL GROUP BY td.task_id + ), + task_regions AS ( + SELECT + tm.task_id, + ARRAY_AGG(DISTINCT u.area ORDER BY u.area) FILTER ( + WHERE u.area IS NOT NULL AND u.area != '' + ) AS evaluation_regions + FROM leaudit_cross_review_task_members tm + JOIN sso_users u ON u.id = tm.user_id + WHERE tm.delete_time IS NULL + GROUP BY tm.task_id ) SELECT t.id AS task_id, @@ -313,16 +324,20 @@ class CrossReviewServiceImpl(ICrossReviewService): t.status, t.create_time, COALESCE(ds.total_documents, 0) AS total_documents, - COALESCE(ds.completed_documents, 0) AS completed_documents + COALESCE(ds.completed_documents, 0) AS completed_documents, + COALESCE(tr.evaluation_regions, ARRAY[]::varchar[]) AS evaluation_regions FROM leaudit_cross_review_tasks t JOIN leaudit_cross_review_task_members tm ON tm.task_id = t.id LEFT JOIN doc_stats ds ON ds.task_id = t.id + LEFT JOIN task_regions tr + ON tr.task_id = t.id WHERE {whereSql} GROUP BY t.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code, - t.status, t.create_time, ds.total_documents, ds.completed_documents + t.status, t.create_time, ds.total_documents, ds.completed_documents, + tr.evaluation_regions ORDER BY t.create_time DESC, t.id DESC LIMIT :limit OFFSET :offset """ @@ -336,6 +351,13 @@ class CrossReviewServiceImpl(ICrossReviewService): totalDocuments = int(row["total_documents"] or 0) completedDocuments = int(row["completed_documents"] or 0) progress = round((completedDocuments / totalDocuments * 100) if totalDocuments > 0 else 0, 2) + rawRegions = row.get("evaluation_regions") + if rawRegions is None: + evaluationRegion: list[str] = [] + elif isinstance(rawRegions, list): + evaluationRegion = [str(r) for r in rawRegions] + else: + evaluationRegion = [str(rawRegions)] items.append( CrossReviewTaskItemVO( taskId=int(row["task_id"]), @@ -348,6 +370,7 @@ class CrossReviewServiceImpl(ICrossReviewService): totalDocuments=totalDocuments, completedDocuments=completedDocuments, createdAt=row.get("create_time"), + evaluationRegion=evaluationRegion, ) ) @@ -453,7 +476,24 @@ class CrossReviewServiceImpl(ICrossReviewService): d.created_at, td.audit_status, COALESCE(dt.name, '') AS type_name, - COALESCE(df.file_size, 0) AS file_size + COALESCE(df.file_size, 0) AS file_size, + COALESCE(df.file_path, '') AS path, + df.file_upload_time AS upload_time, + COALESCE(df.file_ext, '') AS file_ext, + COALESCE(es.total_evaluation_points, 0) AS total_evaluation_points, + COALESCE(es.pass_count, 0) AS pass_count, + COALESCE(es.warning_count, 0) AS warning_count, + COALESCE(es.error_count, 0) AS error_count, + COALESCE(es.manual_count, 0) AS manual_count, + COALESCE(es.issue_count, 0) AS issue_count, + COALESCE(es.warning_messages, ARRAY[]::text[]) AS warning_messages, + COALESCE(es.error_messages, ARRAY[]::text[]) AS error_messages, + COALESCE(es.issue_messages, ARRAY[]::text[]) AS issue_messages, + COALESCE(es.manual_messages, ARRAY[]::text[]) AS manual_messages, + COALESCE(es.final_score, 0) AS final_score, + COALESCE(es.full_score, 0) AS full_score, + COALESCE(es.score_summary, '') AS score_summary, + COALESCE(es.score_percent, 0) AS score_percent FROM leaudit_cross_review_task_documents td JOIN leaudit_documents d ON d.id = td.document_id @@ -462,7 +502,8 @@ class CrossReviewServiceImpl(ICrossReviewService): LEFT JOIN leaudit_document_types dt ON dt.id = d.type_id LEFT JOIN LATERAL ( - SELECT file_size + SELECT file_size, local_path AS file_path, created_at AS file_upload_time, + COALESCE(file_ext, '') AS file_ext FROM leaudit_document_files WHERE document_id = d.id ORDER BY id ASC @@ -478,6 +519,51 @@ class CrossReviewServiceImpl(ICrossReviewService): WHERE d2.deleted_at IS NULL GROUP BY d2.version_group_key ) vc ON vc.version_group_key = d.version_group_key + LEFT JOIN LATERAL ( + SELECT + COUNT(*)::int AS total_evaluation_points, + COUNT(*) FILTER (WHERE rr.passed IS TRUE)::int AS pass_count, + COUNT(*) FILTER (WHERE rr.passed IS FALSE AND rr.risk = 'high')::int AS error_count, + COUNT(*) FILTER (WHERE rr.passed IS FALSE AND rr.risk IN ('low', 'medium'))::int AS warning_count, + 0::int AS manual_count, + COUNT(*) FILTER (WHERE rr.passed IS FALSE)::int AS issue_count, + ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER ( + WHERE rr.passed IS FALSE AND rr.risk = 'high' AND rr.fail_message IS NOT NULL AND rr.fail_message != '' + ) AS error_messages, + ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER ( + WHERE rr.passed IS FALSE AND rr.risk IN ('low', 'medium') AND rr.fail_message IS NOT NULL AND rr.fail_message != '' + ) AS warning_messages, + ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER ( + WHERE rr.passed IS FALSE AND rr.fail_message IS NOT NULL AND rr.fail_message != '' + ) AS issue_messages, + ARRAY[]::text[] AS manual_messages, + COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0) AS final_score, + COALESCE(SUM(rr.score), 0) AS full_score, + CASE + WHEN COALESCE(SUM(rr.score), 0) > 0 + THEN CONCAT( + ROUND( + COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0)::numeric, + 1 + )::text, + '/', + ROUND(COALESCE(SUM(rr.score), 0)::numeric, 1)::text + ) + ELSE '0/0' + END AS score_summary, + CASE + WHEN COALESCE(SUM(rr.score), 0) > 0 + THEN ROUND( + COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0) + / SUM(rr.score) * 100, + 1 + ) + ELSE 0 + END AS score_percent + FROM leaudit_rule_results rr + WHERE rr.document_id = d.id + AND rr.run_id = d.current_run_id + ) es ON TRUE WHERE {whereSql} ORDER BY d.created_at DESC, d.id DESC LIMIT :limit OFFSET :offset @@ -502,6 +588,23 @@ class CrossReviewServiceImpl(ICrossReviewService): auditStatus=int(row.get("audit_status") or 0), createdAt=row.get("created_at"), fileSize=int(row.get("file_size") or 0), + path=str(row.get("path") or ""), + uploadTime=row.get("upload_time"), + fileExt=str(row.get("file_ext") or "") or None, + totalEvaluationPoints=int(row.get("total_evaluation_points") or 0), + passCount=int(row.get("pass_count") or 0), + warningCount=int(row.get("warning_count") or 0), + errorCount=int(row.get("error_count") or 0), + manualCount=int(row.get("manual_count") or 0), + issueCount=int(row.get("issue_count") or 0), + warningMessages=self._parse_text_array(row.get("warning_messages")), + errorMessages=self._parse_text_array(row.get("error_messages")), + issueMessages=self._parse_text_array(row.get("issue_messages")), + manualMessages=self._parse_text_array(row.get("manual_messages")), + finalScore=float(row.get("final_score") or 0), + fullScore=float(row.get("full_score") or 0), + scoreSummary=str(row.get("score_summary") or ""), + scorePercent=float(row.get("score_percent") or 0), ) for row in rows ] @@ -1411,3 +1514,11 @@ class CrossReviewServiceImpl(ICrossReviewService): if value is None: return None return int(value) + + def _parse_text_array(self, value) -> list[str]: + """安全解析 PostgreSQL text[] 为字符串列表。""" + if value is None: + return [] + if isinstance(value, list): + return [str(v) for v in value] + return [str(value)] diff --git a/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py index daa222b..faf4ee4 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py @@ -2366,10 +2366,19 @@ class DocumentServiceImpl(IDocumentService): a.id AS audit_id, a.edit_audit_status, a.override_result, - a.message AS audit_message + a.message AS audit_message, + COALESCE(ad.approved_delta, 0) AS approved_delta FROM leaudit_rule_results rr LEFT JOIN leaudit_review_point_audits a ON a.rule_result_id = rr.id + LEFT JOIN LATERAL ( + SELECT COALESCE(SUM(proposed_score_delta), 0) AS approved_delta + FROM leaudit_cross_review_proposals + WHERE rule_result_id = rr.id + AND document_id = :document_id + AND status = 'approved' + AND delete_time IS NULL + ) ad ON TRUE WHERE rr.run_id = :run_id AND rr.document_id = :document_id ORDER BY id ASC @@ -2429,6 +2438,10 @@ class DocumentServiceImpl(IDocumentService): score=score, finalScore=score if resultFlag is True else (0.0 if resultFlag is False else None), machineScore=score, + currentScore=( + (score if resultFlag is True else 0.0) + + float(row["approved_delta"] or 0) + ), result=resultFlag, failMessage=str(row["fail_message"] or ""), passMessage=str(row["pass_message"] or ""), diff --git a/legal-platform-frontend b/legal-platform-frontend new file mode 160000 index 0000000..f6bb4aa --- /dev/null +++ b/legal-platform-frontend @@ -0,0 +1 @@ +Subproject commit f6bb4aa5524ee4325bdd871c5f7a21b1543f8d80