1520 lines
42 KiB
Markdown
1520 lines
42 KiB
Markdown
# getReviewPoints 方法详细分析
|
||
|
||
## 📋 方法概述
|
||
|
||
**文件位置**:`app/api/evaluation_points/reviews.ts:132-740`
|
||
|
||
**方法签名**:
|
||
```typescript
|
||
export async function getReviewPoints(fileId: string, request: Request)
|
||
```
|
||
|
||
**功能**:获取指定文档的所有评查点结果、统计数据、评查信息以及相关联的文档数据。
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data: ReviewPointResult[], // 评查点结果列表
|
||
stats: StatsData, // 统计数据
|
||
reviewInfo: ReviewInfo, // 评查信息
|
||
document: Document, // 文档数据
|
||
comparison_document: ComparisonDoc, // 合同结构比对数据
|
||
scoring_proposals: ScoringProposal[] // 评分提案(交叉评查)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 执行流程图
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[开始: getReviewPoints] --> B[获取用户会话信息]
|
||
B --> C[查询documents表获取文档数据]
|
||
C --> D[查询contract_structure_comparison表]
|
||
D --> E[查询evaluation_results表]
|
||
E --> F[查询evaluation_points表]
|
||
F --> G[查询evaluation_point_groups表]
|
||
G --> H[查询audit_status表<br/>人工审核状态]
|
||
H --> I[查询cross_scoring_proposals表<br/>评分提案]
|
||
I --> J[构建前端数据格式]
|
||
J --> K[计算统计数据]
|
||
K --> L[构建评查信息]
|
||
L --> M[返回完整数据]
|
||
|
||
style A fill:#90EE90
|
||
style M fill:#FFD700
|
||
style C fill:#87CEEB
|
||
style D fill:#87CEEB
|
||
style E fill:#87CEEB
|
||
style F fill:#87CEEB
|
||
style G fill:#87CEEB
|
||
style H fill:#87CEEB
|
||
style I fill:#87CEEB
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 数据库查询总览
|
||
|
||
### 查询汇总表
|
||
|
||
| 序号 | 表名 | 查询类型 | 查询条件 | 返回记录数 | 查询目的 |
|
||
|-----|------|---------|---------|-----------|---------|
|
||
| 1 | `documents` | 单条查询 | `id = fileId` | 1 | 获取文档基本信息和OCR结果 |
|
||
| 2 | `contract_structure_comparison` | 单条查询 | `document_id = fileId` | 0-1 | 获取合同模板比对结果 |
|
||
| 3 | `evaluation_results` | 多条查询 | `document_id = fileId` | N | 获取所有评查结果(核心数据) |
|
||
| 4 | `evaluation_points` | 批量查询 | `id IN (...)` | N | 获取评查点配置详情 |
|
||
| 5 | `evaluation_point_groups` | 批量查询 | `id IN (...)` | M | 获取评查点分组信息 |
|
||
| 6 | `audit_status` | 批量查询 | `document_id + evaluation_point_id IN (...)` | 0-K | 获取人工审核状态 |
|
||
| 7 | `cross_scoring_proposals` | 多条查询 | `document_id = fileId AND deleted_at IS NULL` | 0-P | 获取交叉评查提案 |
|
||
|
||
**查询特点**:
|
||
- ✅ 采用批量查询策略(IN 操作符),减少数据库往返
|
||
- ✅ 查询顺序经过优化,先查主表,再关联查询
|
||
- ✅ 使用 JWT Token 进行身份验证
|
||
- ✅ 所有查询都通过 PostgREST API 执行
|
||
|
||
---
|
||
|
||
## 🗄️ 数据库查询详解
|
||
|
||
### 1️⃣ 用户会话验证(lines 133-141)
|
||
|
||
**目的**:获取用户身份信息和 JWT token
|
||
|
||
```typescript
|
||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||
```
|
||
|
||
**验证**:
|
||
- 检查 `userInfo.user_id` 是否存在
|
||
- 如果不存在,返回 401 错误
|
||
|
||
---
|
||
|
||
### 2️⃣ 查询文档数据(lines 143-148)
|
||
|
||
**表名**:`documents`
|
||
|
||
**调用方法**:
|
||
```typescript
|
||
const documentData = await getDocumentWithNoUserId(fileId, frontendJWT);
|
||
```
|
||
|
||
**查询参数**:
|
||
- `fileId` - 文档ID
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT *
|
||
FROM documents
|
||
WHERE id = :fileId
|
||
LIMIT 1;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /documents?id=eq.{fileId}&select=*
|
||
Authorization: Bearer {frontendJWT}
|
||
```
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
{
|
||
data: {
|
||
id: string | number,
|
||
name: string, // 文档名称
|
||
path: string, // 文档存储路径
|
||
user_id: string | number, // 上传用户ID
|
||
document_type_id: string | number, // 文档类型ID
|
||
audit_status: number, // 审核状态:0-待审核,1-已审核,-1-不通过
|
||
created_at: string, // 创建时间
|
||
updated_at: string, // 更新时间
|
||
ocrResult: {
|
||
ocr_result: {
|
||
[key: string]: { // 表单名称作为key
|
||
pages: number[] // 该表单出现的页码列表
|
||
}
|
||
}
|
||
},
|
||
// ... 其他文档字段
|
||
}
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": 123,
|
||
"name": "烟草专卖案卷-示例.pdf",
|
||
"path": "/uploads/2024/01/document123.pdf",
|
||
"user_id": 6,
|
||
"document_type_id": 1,
|
||
"audit_status": 0,
|
||
"ocrResult": {
|
||
"ocr_result": {
|
||
"立案报告表": { "pages": [1] },
|
||
"现场笔录": { "pages": [2, 3] },
|
||
"证据复制(提取)单": { "pages": [4, 5] }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取文档基本信息
|
||
- 获取 OCR 结果用于页码映射(lines 393-396)
|
||
- 显示文档名称和路径
|
||
|
||
**错误处理**:
|
||
```typescript
|
||
if (documentData.error) {
|
||
console.error("获取文档数据错误:", documentData.error);
|
||
return Response.json({ error: documentData.error }, { status: documentData.status || 500 });
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3️⃣ 查询合同结构比对数据(lines 151-191)
|
||
|
||
**表名**:`contract_structure_comparison`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'document_id': `eq.${fileId}`
|
||
},
|
||
order: 'id.desc',
|
||
limit: 1,
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 根据 `document_id` 查询
|
||
- 按 `id` 降序排序,取最新的一条记录
|
||
- 限制返回 1 条
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT *
|
||
FROM contract_structure_comparison
|
||
WHERE document_id = :fileId
|
||
ORDER BY id DESC
|
||
LIMIT 1;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /contract_structure_comparison?document_id=eq.{fileId}&order=id.desc&limit=1&select=*
|
||
Authorization: Bearer {frontendJWT}
|
||
```
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
{
|
||
id: string | number,
|
||
document_id: string | number,
|
||
template_contract_path: string, // 模板文件路径
|
||
comparison_results: string | object, // 比对结果(JSON字符串或对象)
|
||
created_at: string,
|
||
updated_at: string,
|
||
// ... 其他字段
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"id": 45,
|
||
"document_id": 123,
|
||
"template_contract_path": "/templates/standard_contract.pdf",
|
||
"comparison_results": {
|
||
"合同封面": [
|
||
{ "field": "合同名称", "status": "normal", "similarity": 1.0 }
|
||
],
|
||
"合同条款": [
|
||
{ "field": "甲方信息", "status": "abnormal", "similarity": 0.65 }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**数据处理**:
|
||
- 如果 `comparison_results` 是字符串,尝试解析为 JSON(lines 179-186)
|
||
```typescript
|
||
if (typeof comparisonDocument.comparison_results === 'string') {
|
||
try {
|
||
comparisonDocument.comparison_results = JSON.parse(comparisonDocument.comparison_results);
|
||
} catch (e) {
|
||
console.error('解析比对结果失败:', e);
|
||
comparisonDocument.comparison_results = null;
|
||
}
|
||
}
|
||
```
|
||
- 如果没有记录,设置默认值 `{ template_contract_path: '' }`(lines 187-191)
|
||
```typescript
|
||
if (!contractStructureComparisonData || contractStructureComparisonData.length === 0) {
|
||
comparisonDocument = { template_contract_path: '' };
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取合同模板比对结果
|
||
- 提供模板文件路径用于对比显示
|
||
- 显示字段匹配状态和相似度
|
||
|
||
---
|
||
|
||
### 4️⃣ 查询评查结果(lines 196-215)
|
||
|
||
**表名**:`evaluation_results` ⭐ **核心表**
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'document_id': `eq.${fileId}`
|
||
},
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 根据 `document_id` 查询该文档的所有评查结果
|
||
- 这是整个方法的核心数据源,后续查询都基于此
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT *
|
||
FROM evaluation_results
|
||
WHERE document_id = :fileId;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /evaluation_results?document_id=eq.{fileId}&select=*
|
||
Authorization: Bearer {frontendJWT}
|
||
```
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
[
|
||
{
|
||
id: string | number,
|
||
document_id: string | number,
|
||
evaluation_point_id: string | number,
|
||
evaluated_results: {
|
||
result: boolean, // 评查结果 true/false
|
||
message: string, // 评查消息
|
||
data: object | string // 评查数据
|
||
},
|
||
evaluated_point_results_log: {
|
||
rules: unknown[] // 规则执行日志
|
||
},
|
||
final_score: number, // 最终得分(交叉评查)
|
||
machine_score: number, // 机器评分
|
||
updated_at: string, // 更新时间
|
||
created_at: string,
|
||
// ... 其他字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
[
|
||
{
|
||
"id": 1001,
|
||
"document_id": 123,
|
||
"evaluation_point_id": 5,
|
||
"evaluated_results": {
|
||
"result": false,
|
||
"message": "立案报告表中的当事人信息与营业执照不一致",
|
||
"data": {
|
||
"立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" },
|
||
"证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" }
|
||
}
|
||
},
|
||
"evaluated_point_results_log": {
|
||
"rules": [
|
||
{
|
||
"id": "0",
|
||
"type": "consistency",
|
||
"res": false,
|
||
"config": {
|
||
"logic": "all",
|
||
"pairs": [
|
||
{
|
||
"sourceField": { "立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" } },
|
||
"targetField": { "证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" } },
|
||
"compareMethod": "exact",
|
||
"res": false
|
||
}
|
||
]
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"final_score": null,
|
||
"machine_score": 5,
|
||
"updated_at": "2024-01-15T10:30:00Z"
|
||
},
|
||
{
|
||
"id": 1002,
|
||
"document_id": 123,
|
||
"evaluation_point_id": 8,
|
||
"evaluated_results": {
|
||
"result": true,
|
||
"message": "签名完整性检查通过",
|
||
"data": {
|
||
"现场笔录-执法人员签名": { "page": 3, "value": "有" }
|
||
}
|
||
},
|
||
"evaluated_point_results_log": {
|
||
"rules": []
|
||
},
|
||
"final_score": null,
|
||
"machine_score": 10,
|
||
"updated_at": "2024-01-15T10:30:00Z"
|
||
}
|
||
]
|
||
```
|
||
|
||
**数据验证**:
|
||
- 如果没有评查结果,返回空数组和空统计(lines 213-215)
|
||
```typescript
|
||
if (Array.isArray(evaluationResultsData) && evaluationResultsData.length <= 0) {
|
||
return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 }, error: '获取评查结果数据失败' };
|
||
}
|
||
```
|
||
|
||
**关键字段说明**:
|
||
- `evaluated_results.result` - 评查通过/不通过(true/false)
|
||
- `evaluated_results.message` - 评查点标题/问题描述
|
||
- `evaluated_results.data` - 具体的评查内容(字段值和页码)
|
||
- 格式:`{ "字段名": { "page": 页码, "value": 字段值 } }`
|
||
- `evaluated_point_results_log.rules` - 规则执行日志(用于调试和详情展示)
|
||
|
||
**数据使用**:
|
||
- 从中提取 `evaluation_point_id` 列表 → 查询 evaluation_points
|
||
- `updated_at` 用于确定最新评查时间
|
||
- `evaluated_results.data` 用于页码映射
|
||
|
||
---
|
||
|
||
### 5️⃣ 查询评查点详情(lines 218-242)
|
||
|
||
**表名**:`evaluation_points`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'id': `in.(${evaluationPointIds.join(',')})`
|
||
},
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 从评查结果中提取所有 `evaluation_point_id`(line 218)
|
||
- 使用 `IN` 操作符批量查询评查点详情
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
[
|
||
{
|
||
id: string | number,
|
||
evaluation_point_groups_id: string | number,
|
||
name: string, // 评查点名称
|
||
suggestion_message_type: string, // 建议消息类型(success/warning/error)
|
||
suggestion_message: string, // 建议内容
|
||
score: number, // 满分分数
|
||
post_action: string, // 后续动作(manual/auto)
|
||
action_config: string | object, // 动作配置
|
||
references_laws: object, // 法律依据
|
||
evaluation_config: object, // 评查配置
|
||
fail_message: string, // 不通过提示
|
||
pass_message: string, // 通过提示
|
||
updated_at: string,
|
||
// ... 其他字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取评查点配置信息
|
||
- 获取评分标准
|
||
- 获取建议内容和法律依据
|
||
|
||
---
|
||
|
||
### 6️⃣ 查询评查点组(lines 244-269)
|
||
|
||
**表名**:`evaluation_point_groups`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'id': `in.(${groupIds.join(',')})`
|
||
},
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 从评查点中提取所有 `evaluation_point_groups_id`(line 245)
|
||
- 使用 `IN` 操作符批量查询评查点组
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
[
|
||
{
|
||
id: string | number,
|
||
name: string, // 评查点组名称
|
||
// ... 其他字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取评查点所属的分组名称(如"合同形式要素"、"合同实质要素"等)
|
||
|
||
---
|
||
|
||
### 7️⃣ 查询人工审核状态(lines 272-308)
|
||
|
||
**表名**:`audit_status`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'document_id': `eq.${fileId}`,
|
||
'evaluation_point_id': `in.(${manualReviewPointsIds.join(',')})`
|
||
},
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 筛选出 `post_action === 'manual'` 的评查点(line 273)
|
||
- 只查询需要人工审核的评查点的审核状态
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
[
|
||
{
|
||
id: string | number,
|
||
document_id: string | number,
|
||
evaluation_point_id: string | number,
|
||
edit_audit_status: number, // 审核状态:0-待审核,1-已审核
|
||
message: string, // 审核意见
|
||
// ... 其他字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**数据处理**:
|
||
- 构建 `editAuditStatusMap` 映射表(lines 290-298)
|
||
- 为没有审核记录的 manual 评查点设置默认值 0(lines 302-308)
|
||
|
||
**使用场景**:
|
||
- 判断哪些评查点需要人工审核
|
||
- 显示审核状态和审核意见
|
||
|
||
---
|
||
|
||
### 8️⃣ 查询评分提案(lines 330-343)
|
||
|
||
**表名**:`cross_scoring_proposals`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: '*',
|
||
filter: {
|
||
'document_id': `eq.${fileId}`,
|
||
'deleted_at': `is.null`
|
||
},
|
||
token: frontendJWT
|
||
}
|
||
```
|
||
|
||
**查询逻辑**:
|
||
- 根据 `document_id` 查询
|
||
- 排除已删除的提案(`deleted_at IS NULL`)
|
||
|
||
**返回数据结构**:
|
||
```typescript
|
||
[
|
||
{
|
||
id: string | number,
|
||
evaluation_result_id: string | number,
|
||
proposer_id: string | number,
|
||
proposed_score: number, // 建议分数
|
||
reason: string, // 提议理由
|
||
status: string, // 提案状态
|
||
created_at: string,
|
||
updated_at: string,
|
||
document_id: string | number,
|
||
// ... 其他字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 交叉评查功能
|
||
- 显示评分提案和投票情况
|
||
|
||
---
|
||
|
||
## 🔧 数据构建与处理
|
||
|
||
### 9️⃣ 构建前端数据格式(lines 350-685)
|
||
|
||
**目标**:将多表查询结果合并为前端所需的数据格式
|
||
|
||
**核心逻辑**:
|
||
|
||
#### a) 创建映射表(lines 314-327)
|
||
```typescript
|
||
// 评查点映射
|
||
const pointsMap = new Map<string | number, EvaluationPoint>();
|
||
evaluationPointsData.forEach(point => {
|
||
pointsMap.set(point.id, point);
|
||
});
|
||
|
||
// 评查点组映射
|
||
const groupsMap = new Map<string | number, EvaluationPointGroup>();
|
||
groupsData.forEach(group => {
|
||
groupsMap.set(group.id, group);
|
||
});
|
||
```
|
||
|
||
#### b) 遍历评查结果构建数据(line 351)
|
||
```typescript
|
||
const resultData: ReviewPointResult[] = evaluationResultsData.map(result => {
|
||
// ...
|
||
});
|
||
```
|
||
|
||
#### c) 提取评查内容和页码(lines 361-403)
|
||
|
||
**步骤**:
|
||
1. 从 `evaluated_results` 中提取 `message` 和 `data`(lines 361-368)
|
||
2. 解析 `data` 对象中的页码信息(lines 375-403)
|
||
|
||
**页码提取逻辑**:
|
||
```typescript
|
||
// data 结构示例:
|
||
// {
|
||
// "合同封面-合同名称": { page: 1, value: "采购合同" },
|
||
// "合同封面-签订日期": { page: 1, value: "2024-01-01" }
|
||
// }
|
||
|
||
// 提取页码
|
||
for (const key in dataObj) {
|
||
let newPage = dataObj[key].page.toString();
|
||
// 提取页码中的数字部分
|
||
if (newPage.match(/\d+/g)) {
|
||
newPage = newPage.match(/^\d+/g)?.map(Number).join('') || '';
|
||
}
|
||
contentPage[key] = newPage;
|
||
|
||
// 如果页码为空,从 ocrResult 中查找
|
||
if (!contentPage[key]) {
|
||
const keyArray = key.split('-');
|
||
const ocrResult = documentData?.data?.ocrResult as OcrData;
|
||
const pages = ocrResult?.ocr_result?.[keyArray[0]]?.pages;
|
||
contentPage[key] = pages?.[0]?.toString() || '';
|
||
}
|
||
}
|
||
```
|
||
|
||
#### d) 构建单个评查点结果对象(lines 405-684)
|
||
|
||
**返回的字段**:
|
||
```typescript
|
||
{
|
||
// 基本信息
|
||
id: result.id, // 评查结果ID
|
||
documentId: fileId, // 文档ID
|
||
pointId: point.id, // 评查点ID
|
||
|
||
// 审核状态
|
||
editAuditStatusId: editAuditStatus.id, // 审核状态ID
|
||
editAuditStatus: editAuditStatus.status, // 审核状态:0-待审核,1-已审核
|
||
editAuditStatusMessage: editAuditStatus.message, // 审核意见
|
||
|
||
// 显示信息
|
||
title: message, // 评查点标题/问题描述
|
||
pointName: point.name || '', // 评查点名称
|
||
groupName: group.name || '', // 评查点组名称
|
||
status: point.suggestion_message_type || '', // 评查状态:success/warning/error
|
||
|
||
// 内容数据
|
||
content: data, // 评查内容(原始数据)
|
||
contentPage: contentPage, // 页码映射
|
||
|
||
// 建议和配置
|
||
suggestion: point.suggestion_message || '', // 建议内容
|
||
postAction: point.post_action || '', // 后续动作:manual/auto
|
||
actionContent: point.action_config || '', // 动作配置
|
||
legalBasis: point.references_laws || {}, // 法律依据
|
||
evaluationConfig: point.evaluation_config || {}, // 评查配置
|
||
|
||
// 评分信息
|
||
score: point.score || 0, // 满分分数
|
||
finalScore: result.final_score, // 最终得分(交叉评查)
|
||
machineScore: result.machine_score, // 机器评分
|
||
result: result.evaluated_results?.result, // 评查结果:true/false
|
||
|
||
// 交叉评查
|
||
failMessage: point.fail_message || '', // 不通过提示
|
||
passMessage: point.pass_message || '', // 通过提示
|
||
|
||
// 日志
|
||
evaluatedPointResultsLog: evaluatedPointResultsLog || {} // 规则执行日志
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 🔟 计算统计数据(lines 687-712)
|
||
|
||
**统计维度**:
|
||
```typescript
|
||
const stats: StatsData = {
|
||
total: 0, // 总评查点数量
|
||
success: 0, // 通过数量
|
||
warning: 0, // 警告数量
|
||
error: 0, // 错误数量
|
||
score: 0 // 总分数
|
||
};
|
||
```
|
||
|
||
**计算逻辑**:
|
||
```typescript
|
||
resultData.forEach(item => {
|
||
// 1. 成功数量统计
|
||
if (item.result === true) {
|
||
stats.success += 1;
|
||
}
|
||
// 2. 警告和错误数量统计
|
||
else if (item.result === false) {
|
||
if (item.status === 'warning') {
|
||
stats.warning += 1;
|
||
} else if (item.status === 'error') {
|
||
stats.error += 1;
|
||
}
|
||
}
|
||
|
||
// 3. 分数累加
|
||
stats.score += item.score || 0;
|
||
});
|
||
```
|
||
|
||
**统计说明**:
|
||
- `total` = `evaluationResultsData.length`(line 689)
|
||
- `success` = `result === true` 的数量
|
||
- `warning` = `result === false && status === 'warning'` 的数量
|
||
- `error` = `result === false && status === 'error'` 的数量
|
||
- `score` = 所有评查点满分之和
|
||
|
||
---
|
||
|
||
### 1️⃣1️⃣ 构建评查信息(lines 714-736)
|
||
|
||
**目标**:生成评查总览信息
|
||
|
||
**步骤**:
|
||
|
||
#### a) 找出最新评查时间(lines 716-721)
|
||
```typescript
|
||
let latestUpdatedAt = '';
|
||
evaluationResultsData.forEach(result => {
|
||
if (result.updated_at && (!latestUpdatedAt || result.updated_at > latestUpdatedAt)) {
|
||
latestUpdatedAt = result.updated_at.toString();
|
||
}
|
||
});
|
||
```
|
||
|
||
#### b) 提取不重复的规则组名称(line 724)
|
||
```typescript
|
||
const uniqueGroups = Array.from(new Set(resultData.map(item => item.groupName))).filter(Boolean);
|
||
```
|
||
|
||
#### c) 计算问题数量(line 727)
|
||
```typescript
|
||
const issueCount = stats.warning + stats.error;
|
||
```
|
||
|
||
#### d) 构建评查信息对象(lines 730-736)
|
||
```typescript
|
||
const reviewInfo = {
|
||
reviewTime: dayjs.utc(latestUpdatedAt).format('YYYY-MM-DD HH:mm:ss'), // 评查时间
|
||
reviewModel: 'DeepSeek', // 评查模型
|
||
ruleGroup: uniqueGroups.join('、'), // 规则组名称(用顿号分隔)
|
||
result: issueCount > 0 ? 'warning' : 'success', // 总体结果
|
||
issueCount: issueCount // 问题数量
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 数据流转图
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[evaluation_results] --> B[提取 evaluation_point_id]
|
||
B --> C[查询 evaluation_points]
|
||
C --> D[提取 evaluation_point_groups_id]
|
||
D --> E[查询 evaluation_point_groups]
|
||
|
||
A --> F[提取 manual 类型的评查点]
|
||
F --> G[查询 audit_status]
|
||
|
||
A --> H[构建 resultData]
|
||
C --> H
|
||
E --> H
|
||
G --> H
|
||
|
||
H --> I[计算 stats]
|
||
I --> J[构建 reviewInfo]
|
||
|
||
K[documents] --> H
|
||
L[contract_structure_comparison] --> M[返回]
|
||
N[cross_scoring_proposals] --> M
|
||
|
||
H --> M
|
||
I --> M
|
||
J --> M
|
||
K --> M
|
||
|
||
style A fill:#87CEEB
|
||
style C fill:#87CEEB
|
||
style E fill:#87CEEB
|
||
style G fill:#87CEEB
|
||
style K fill:#87CEEB
|
||
style L fill:#87CEEB
|
||
style N fill:#87CEEB
|
||
style M fill:#FFD700
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 关键数据关系
|
||
|
||
### 表关系图
|
||
|
||
```
|
||
documents (文档表)
|
||
↓ (1:N)
|
||
evaluation_results (评查结果表)
|
||
↓ (N:1)
|
||
evaluation_points (评查点表)
|
||
↓ (N:1)
|
||
evaluation_point_groups (评查点组表)
|
||
|
||
evaluation_results + evaluation_points (manual 类型)
|
||
↓ (1:1)
|
||
audit_status (人工审核状态表)
|
||
|
||
documents
|
||
↓ (1:1)
|
||
contract_structure_comparison (合同结构比对表)
|
||
|
||
documents + evaluation_results
|
||
↓ (1:N)
|
||
cross_scoring_proposals (评分提案表)
|
||
```
|
||
|
||
### 字段映射关系
|
||
|
||
| 源表 | 源字段 | 目标表 | 目标字段 |
|
||
|-----|-------|-------|---------|
|
||
| evaluation_results | evaluation_point_id | evaluation_points | id |
|
||
| evaluation_points | evaluation_point_groups_id | evaluation_point_groups | id |
|
||
| evaluation_results | document_id + evaluation_point_id | audit_status | document_id + evaluation_point_id |
|
||
| documents | id | contract_structure_comparison | document_id |
|
||
| documents | id | cross_scoring_proposals | document_id |
|
||
|
||
---
|
||
|
||
## 🚨 错误处理
|
||
|
||
### 错误返回格式
|
||
```typescript
|
||
{
|
||
error: string,
|
||
status: number
|
||
}
|
||
```
|
||
|
||
### 错误场景
|
||
|
||
| 错误场景 | 返回内容 | 状态码 |
|
||
|---------|---------|--------|
|
||
| 用户未认证 | `{ error: '用户身份验证失败' }` | 401 |
|
||
| 文档不存在 | `{ error: documentData.error }` | 500 |
|
||
| 合同比对数据错误 | `{ error: contractStructureComparisonResponse.error }` | 500 |
|
||
| 评查结果为空 | `{ data: [], stats: {...}, error: '获取评查结果数据失败' }` | - |
|
||
| 评查点ID为空 | `{ data: [], stats: {...}, error: '获取评查点ID失败' }` | - |
|
||
|
||
---
|
||
|
||
## 💡 核心业务逻辑
|
||
|
||
### 1. 评查状态判断逻辑
|
||
|
||
```typescript
|
||
// 评查结果 (result) 决定是否通过
|
||
if (item.result === true) {
|
||
// 通过
|
||
stats.success += 1;
|
||
} else if (item.result === false) {
|
||
// 未通过,根据 status 区分严重程度
|
||
if (item.status === 'warning') {
|
||
stats.warning += 1; // 警告
|
||
} else if (item.status === 'error') {
|
||
stats.error += 1; // 错误
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 页码提取优先级
|
||
|
||
```
|
||
1. 优先从 data[key].page 提取
|
||
2. 提取数字部分(去除文本)
|
||
3. 如果为空,从 ocrResult 中根据 key 前缀查找
|
||
4. 仍然为空,设为空字符串
|
||
```
|
||
|
||
### 3. 人工审核默认值设置
|
||
|
||
```typescript
|
||
// 对于 post_action === 'manual' 的评查点
|
||
// 如果没有 audit_status 记录,设置默认值
|
||
{
|
||
id: '',
|
||
status: 0, // 0 表示待审核
|
||
message: ''
|
||
}
|
||
```
|
||
|
||
### 4. 总体评查结果判断
|
||
|
||
```typescript
|
||
const issueCount = stats.warning + stats.error;
|
||
const result = issueCount > 0 ? 'warning' : 'success';
|
||
```
|
||
|
||
**规则**:
|
||
- 有任何 warning 或 error → `'warning'`
|
||
- 全部通过 → `'success'`
|
||
|
||
---
|
||
|
||
## 📈 性能优化点
|
||
|
||
### 批量查询策略
|
||
|
||
1. **收集ID后批量查询**:
|
||
```typescript
|
||
// 不好的做法:循环内单独查询
|
||
for (const result of evaluationResultsData) {
|
||
await postgrestGet('evaluation_points', { filter: { id: result.evaluation_point_id } });
|
||
}
|
||
|
||
// 好的做法:收集ID后批量查询(当前实现)
|
||
const evaluationPointIds = evaluationResultsData.map(item => item.evaluation_point_id);
|
||
await postgrestGet('evaluation_points', {
|
||
filter: { 'id': `in.(${evaluationPointIds.join(',')})` }
|
||
});
|
||
```
|
||
|
||
2. **使用 Map 进行快速查找**:
|
||
```typescript
|
||
// O(1) 时间复杂度查找
|
||
const pointsMap = new Map();
|
||
const point = pointsMap.get(evaluation_point_id);
|
||
```
|
||
|
||
### 查询顺序优化
|
||
|
||
```
|
||
1. 先查评查结果 → 获取所有相关ID
|
||
2. 批量查询关联数据 → 减少数据库往返次数
|
||
3. 内存中构建数据 → Map 映射快速关联
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 调试建议
|
||
|
||
### 关键日志位置
|
||
|
||
1. **文档附件数据**:line 170
|
||
```typescript
|
||
console.log('文档附件的数据', JSON.stringify(contractStructureComparisonData, null, 2));
|
||
```
|
||
|
||
2. **评查信息**:line 737
|
||
```typescript
|
||
console.log("reviewInfo-------", JSON.stringify(reviewInfo, null, 2));
|
||
```
|
||
|
||
3. **已注释的调试日志**:
|
||
- line 161: `contract_structure_comparison` 响应
|
||
- line 194: `documentData`
|
||
- line 205: `evaluationResultsResponse`
|
||
- line 272: `manualReviewPoints`
|
||
- line 310: `manualReviewPoints`
|
||
- line 319: `pointsMap`
|
||
- line 326: `groupsMap`
|
||
- line 358: `evaluatedPointResultsLog`
|
||
- line 372-374: `result`, `datacontent`, `documentData`
|
||
|
||
### 建议添加日志的位置
|
||
|
||
```typescript
|
||
// 1. 查询结果数量
|
||
console.log(`📊 评查结果数量: ${evaluationResultsData.length}`);
|
||
|
||
// 2. 各类评查点统计
|
||
console.log(`📊 统计数据:`, stats);
|
||
|
||
// 3. 人工审核评查点数量
|
||
console.log(`👤 需人工审核: ${manualReviewPointsIds.length} 个`);
|
||
|
||
// 4. 评分提案数量
|
||
console.log(`📝 评分提案: ${scoringProposalsData.length} 个`);
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 使用示例
|
||
|
||
### 调用示例
|
||
|
||
```typescript
|
||
// 在 Remix loader 中调用
|
||
export async function loader({ params, request }: LoaderFunctionArgs) {
|
||
const fileId = params.fileId;
|
||
|
||
if (!fileId) {
|
||
return json({ error: '文档ID不能为空' }, { status: 400 });
|
||
}
|
||
|
||
const result = await getReviewPoints(fileId, request);
|
||
|
||
if (result.error) {
|
||
return json({ error: result.error }, { status: result.status || 500 });
|
||
}
|
||
|
||
return json(result);
|
||
}
|
||
```
|
||
|
||
### 前端使用示例
|
||
|
||
```typescript
|
||
// 在 React 组件中使用
|
||
const { data, stats, reviewInfo, document, comparison_document } = useLoaderData<typeof loader>();
|
||
|
||
// 显示统计信息
|
||
<div>
|
||
<p>总计: {stats.total}</p>
|
||
<p>通过: {stats.success}</p>
|
||
<p>警告: {stats.warning}</p>
|
||
<p>错误: {stats.error}</p>
|
||
<p>总分: {stats.score}</p>
|
||
</div>
|
||
|
||
// 显示评查信息
|
||
<div>
|
||
<p>评查时间: {reviewInfo.reviewTime}</p>
|
||
<p>评查模型: {reviewInfo.reviewModel}</p>
|
||
<p>规则组: {reviewInfo.ruleGroup}</p>
|
||
<p>总体结果: {reviewInfo.result}</p>
|
||
<p>问题数量: {reviewInfo.issueCount}</p>
|
||
</div>
|
||
|
||
// 遍历评查点
|
||
{data.map(point => (
|
||
<div key={point.id}>
|
||
<h3>{point.title}</h3>
|
||
<p>状态: {point.status}</p>
|
||
<p>建议: {point.suggestion}</p>
|
||
{/* 更多字段... */}
|
||
</div>
|
||
))}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎓 总结
|
||
|
||
### 核心功能
|
||
|
||
1. **多表联查**:关联查询 8 张表的数据
|
||
2. **数据聚合**:将分散的数据聚合为前端友好的格式
|
||
3. **统计计算**:实时计算评查统计数据
|
||
4. **智能映射**:通过 Map 实现高效数据关联
|
||
5. **页码提取**:智能提取和匹配页码信息
|
||
|
||
### 适用场景
|
||
|
||
- ✅ 文档评查结果展示页面
|
||
- ✅ 评查点详情查看
|
||
- ✅ 人工审核功能
|
||
- ✅ 交叉评查功能
|
||
- ✅ 评查统计分析
|
||
|
||
### 性能特点
|
||
|
||
- **批量查询**:减少数据库往返次数
|
||
- **内存映射**:O(1) 时间复杂度查找
|
||
- **并行处理**:多个无依赖查询可并行执行
|
||
|
||
### 扩展性
|
||
|
||
- ✅ 支持新增评查点类型
|
||
- ✅ 支持自定义统计维度
|
||
- ✅ 支持多种评查模式(自动/人工/交叉)
|
||
|
||
---
|
||
|
||
## 📦 完整返回数据结构详解
|
||
|
||
### 返回数据类型定义
|
||
|
||
```typescript
|
||
interface GetReviewPointsResult {
|
||
data: ReviewPointResult[]; // 评查点结果列表
|
||
stats: StatsData; // 统计数据
|
||
reviewInfo: ReviewInfo; // 评查信息
|
||
document: Document; // 文档数据
|
||
comparison_document: ComparisonDoc; // 合同结构比对数据
|
||
scoring_proposals: ScoringProposal[]; // 评分提案(交叉评查)
|
||
}
|
||
```
|
||
|
||
### 完整返回数据示例
|
||
|
||
```json
|
||
{
|
||
"data": [
|
||
{
|
||
"id": 1001,
|
||
"documentId": "123",
|
||
"pointId": 5,
|
||
"editAuditStatusId": "",
|
||
"editAuditStatus": 0,
|
||
"editAuditStatusMessage": "",
|
||
"title": "立案报告表中的当事人信息与营业执照不一致",
|
||
"pointName": "当事人信息一致性检查",
|
||
"groupName": "合同形式要素",
|
||
"status": "error",
|
||
"content": {
|
||
"立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" },
|
||
"证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" }
|
||
},
|
||
"contentPage": {
|
||
"立案报告表-当事人-单位-名称": "1",
|
||
"证据复制(提取)单-营业执照-名称": "4"
|
||
},
|
||
"suggestion": "请核对当事人信息,确保立案报告表与营业执照信息一致",
|
||
"postAction": "manual",
|
||
"actionContent": "",
|
||
"legalBasis": {
|
||
"name": "烟草专卖法",
|
||
"articles": ["第三十七条", "第三十八条"]
|
||
},
|
||
"evaluationConfig": {
|
||
"type": "consistency",
|
||
"compareMethod": "exact"
|
||
},
|
||
"score": 5,
|
||
"finalScore": null,
|
||
"machineScore": 5,
|
||
"result": false,
|
||
"failMessage": "当事人信息不一致",
|
||
"passMessage": "当事人信息一致",
|
||
"evaluatedPointResultsLog": {
|
||
"rules": [
|
||
{
|
||
"id": "0",
|
||
"type": "consistency",
|
||
"res": false,
|
||
"config": {
|
||
"logic": "all",
|
||
"pairs": [
|
||
{
|
||
"sourceField": { "立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" } },
|
||
"targetField": { "证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" } },
|
||
"compareMethod": "exact",
|
||
"res": false
|
||
}
|
||
]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},
|
||
{
|
||
"id": 1002,
|
||
"documentId": "123",
|
||
"pointId": 8,
|
||
"editAuditStatusId": "201",
|
||
"editAuditStatus": 1,
|
||
"editAuditStatusMessage": "已人工审核确认",
|
||
"title": "签名完整性检查通过",
|
||
"pointName": "执法人员签名检查",
|
||
"groupName": "程序合法性",
|
||
"status": "success",
|
||
"content": {
|
||
"现场笔录-执法人员签名": { "page": 3, "value": "有" }
|
||
},
|
||
"contentPage": {
|
||
"现场笔录-执法人员签名": "3"
|
||
},
|
||
"suggestion": "",
|
||
"postAction": "manual",
|
||
"actionContent": "",
|
||
"legalBasis": {},
|
||
"evaluationConfig": {},
|
||
"score": 10,
|
||
"finalScore": null,
|
||
"machineScore": 10,
|
||
"result": true,
|
||
"failMessage": "",
|
||
"passMessage": "签名完整",
|
||
"evaluatedPointResultsLog": {
|
||
"rules": []
|
||
}
|
||
}
|
||
],
|
||
"stats": {
|
||
"total": 15,
|
||
"success": 12,
|
||
"warning": 2,
|
||
"error": 1,
|
||
"score": 100
|
||
},
|
||
"reviewInfo": {
|
||
"reviewTime": "2024-01-15 10:30:00",
|
||
"reviewModel": "DeepSeek",
|
||
"ruleGroup": "合同形式要素、合同实质要素、程序合法性",
|
||
"result": "warning",
|
||
"issueCount": 3
|
||
},
|
||
"document": {
|
||
"id": 123,
|
||
"name": "烟草专卖案卷-示例.pdf",
|
||
"path": "/uploads/2024/01/document123.pdf",
|
||
"user_id": 6,
|
||
"document_type_id": 1,
|
||
"audit_status": 0,
|
||
"created_at": "2024-01-15T09:00:00Z",
|
||
"updated_at": "2024-01-15T10:30:00Z",
|
||
"ocrResult": {
|
||
"ocr_result": {
|
||
"立案报告表": { "pages": [1] },
|
||
"现场笔录": { "pages": [2, 3] },
|
||
"证据复制(提取)单": { "pages": [4, 5] }
|
||
}
|
||
}
|
||
},
|
||
"comparison_document": {
|
||
"id": 45,
|
||
"document_id": 123,
|
||
"template_contract_path": "/templates/standard_contract.pdf",
|
||
"comparison_results": {
|
||
"合同封面": [
|
||
{ "field": "合同名称", "status": "normal", "similarity": 1.0 }
|
||
],
|
||
"合同条款": [
|
||
{ "field": "甲方信息", "status": "abnormal", "similarity": 0.65 }
|
||
]
|
||
}
|
||
},
|
||
"scoring_proposals": [
|
||
{
|
||
"id": 301,
|
||
"evaluation_result_id": 1001,
|
||
"proposer_id": 12,
|
||
"proposed_score": 3,
|
||
"reason": "虽然信息不一致,但属于笔误,建议降低扣分",
|
||
"status": "pending",
|
||
"created_at": "2024-01-15T11:00:00Z",
|
||
"updated_at": "2024-01-15T11:00:00Z",
|
||
"document_id": 123
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 返回字段详细说明
|
||
|
||
#### 1. data 数组(ReviewPointResult[])
|
||
|
||
每个评查点结果对象包含以下字段:
|
||
|
||
| 字段名 | 类型 | 说明 | 来源表 |
|
||
|-------|------|------|--------|
|
||
| `id` | string\|number | 评查结果ID | evaluation_results.id |
|
||
| `documentId` | string | 文档ID | 参数 fileId |
|
||
| `pointId` | string\|number | 评查点ID | evaluation_points.id |
|
||
| `editAuditStatusId` | string\|number | 审核状态ID | audit_status.id |
|
||
| `editAuditStatus` | number | 审核状态(0-待审核,1-已审核) | audit_status.edit_audit_status |
|
||
| `editAuditStatusMessage` | string | 审核意见 | audit_status.message |
|
||
| `title` | string | 评查点标题/问题描述 | evaluation_results.evaluated_results.message |
|
||
| `pointName` | string | 评查点名称 | evaluation_points.name |
|
||
| `groupName` | string | 评查点组名称 | evaluation_point_groups.name |
|
||
| `status` | string | 评查状态(success/warning/error) | evaluation_points.suggestion_message_type |
|
||
| `content` | object | 评查内容(原始数据) | evaluation_results.evaluated_results.data |
|
||
| `contentPage` | object | 页码映射 | 从 content 提取 + ocrResult 补充 |
|
||
| `suggestion` | string | 建议内容 | evaluation_points.suggestion_message |
|
||
| `postAction` | string | 后续动作(manual/auto) | evaluation_points.post_action |
|
||
| `actionContent` | string\|object | 动作配置 | evaluation_points.action_config |
|
||
| `legalBasis` | object | 法律依据 | evaluation_points.references_laws |
|
||
| `evaluationConfig` | object | 评查配置 | evaluation_points.evaluation_config |
|
||
| `score` | number | 满分分数 | evaluation_points.score |
|
||
| `finalScore` | number\|null | 最终得分(交叉评查) | evaluation_results.final_score |
|
||
| `machineScore` | number | 机器评分 | evaluation_results.machine_score |
|
||
| `result` | boolean | 评查结果(true/false) | evaluation_results.evaluated_results.result |
|
||
| `failMessage` | string | 不通过提示 | evaluation_points.fail_message |
|
||
| `passMessage` | string | 通过提示 | evaluation_points.pass_message |
|
||
| `evaluatedPointResultsLog` | object | 规则执行日志 | evaluation_results.evaluated_point_results_log |
|
||
|
||
#### 2. stats 对象(StatsData)
|
||
|
||
| 字段名 | 类型 | 说明 | 计算方式 |
|
||
|-------|------|------|---------|
|
||
| `total` | number | 总评查点数量 | evaluationResultsData.length |
|
||
| `success` | number | 通过数量 | result === true 的数量 |
|
||
| `warning` | number | 警告数量 | result === false && status === 'warning' |
|
||
| `error` | number | 错误数量 | result === false && status === 'error' |
|
||
| `score` | number | 总分数 | 所有评查点 score 之和 |
|
||
|
||
#### 3. reviewInfo 对象(ReviewInfo)
|
||
|
||
| 字段名 | 类型 | 说明 | 计算方式 |
|
||
|-------|------|------|---------|
|
||
| `reviewTime` | string | 评查时间 | 评查结果中最新的 updated_at |
|
||
| `reviewModel` | string | 评查模型 | 固定值 'DeepSeek' |
|
||
| `ruleGroup` | string | 规则组名称 | 所有不重复的 groupName,用顿号分隔 |
|
||
| `result` | string | 总体结果(success/warning) | issueCount > 0 ? 'warning' : 'success' |
|
||
| `issueCount` | number | 问题数量 | stats.warning + stats.error |
|
||
|
||
#### 4. document 对象(Document)
|
||
|
||
文档基本信息,包含:
|
||
- 文档元数据(id, name, path等)
|
||
- OCR 识别结果(ocrResult)
|
||
|
||
#### 5. comparison_document 对象(ComparisonDoc)
|
||
|
||
合同结构比对数据,包含:
|
||
- 模板文件路径(template_contract_path)
|
||
- 比对结果(comparison_results)
|
||
|
||
#### 6. scoring_proposals 数组(ScoringProposal[])
|
||
|
||
交叉评查评分提案列表,每个提案包含:
|
||
- 提案ID、提议人、建议分数、理由
|
||
- 提案状态、创建时间等
|
||
|
||
---
|
||
|
||
## 🔄 数据依赖关系详解
|
||
|
||
### 查询依赖链
|
||
|
||
```
|
||
1. getUserSession (认证)
|
||
↓
|
||
2. documents (fileId) → 获取文档数据
|
||
↓
|
||
3. contract_structure_comparison (document_id) → 获取比对数据
|
||
↓
|
||
4. evaluation_results (document_id) → 获取评查结果 ⭐核心
|
||
↓
|
||
├─→ 5. evaluation_points (evaluation_point_id IN [...])
|
||
│ ↓
|
||
│ └─→ 6. evaluation_point_groups (evaluation_point_groups_id IN [...])
|
||
│
|
||
├─→ 7. audit_status (document_id + evaluation_point_id IN [...])
|
||
│ ↑ 仅查询 post_action === 'manual' 的评查点
|
||
│
|
||
└─→ 8. cross_scoring_proposals (document_id)
|
||
```
|
||
|
||
### 数据合并流程
|
||
|
||
```
|
||
evaluation_results (N条)
|
||
+ evaluation_points (N条) → pointsMap
|
||
+ evaluation_point_groups (M条) → groupsMap
|
||
+ audit_status (K条) → editAuditStatusMap
|
||
↓
|
||
遍历 evaluation_results,通过 Map 快速查找关联数据
|
||
↓
|
||
构建 resultData 数组(N条 ReviewPointResult)
|
||
↓
|
||
计算 stats 统计数据
|
||
↓
|
||
构建 reviewInfo 评查信息
|
||
↓
|
||
返回完整数据
|
||
```
|
||
|
||
### 关键ID映射关系
|
||
|
||
| 源ID | 用于查询 | 目标ID | 关系类型 |
|
||
|------|---------|--------|---------|
|
||
| `fileId` | documents | `documents.id` | 1:1 |
|
||
| `fileId` | contract_structure_comparison | `contract_structure_comparison.document_id` | 1:1 |
|
||
| `fileId` | evaluation_results | `evaluation_results.document_id` | 1:N |
|
||
| `evaluation_results.evaluation_point_id` | evaluation_points | `evaluation_points.id` | N:1 |
|
||
| `evaluation_points.evaluation_point_groups_id` | evaluation_point_groups | `evaluation_point_groups.id` | N:1 |
|
||
| `evaluation_results.evaluation_point_id` (manual) | audit_status | `audit_status.evaluation_point_id` | 1:1 |
|
||
| `fileId` | cross_scoring_proposals | `cross_scoring_proposals.document_id` | 1:N |
|
||
|
||
---
|
||
|
||
## 🔍 查询执行时序图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client
|
||
participant getReviewPoints
|
||
participant DB
|
||
|
||
Client->>getReviewPoints: 调用(fileId, request)
|
||
|
||
getReviewPoints->>DB: 1. getUserSession (验证)
|
||
DB-->>getReviewPoints: userInfo + JWT
|
||
|
||
getReviewPoints->>DB: 2. SELECT * FROM documents WHERE id = fileId
|
||
DB-->>getReviewPoints: document (1条)
|
||
|
||
getReviewPoints->>DB: 3. SELECT * FROM contract_structure_comparison WHERE document_id = fileId
|
||
DB-->>getReviewPoints: comparison (0-1条)
|
||
|
||
getReviewPoints->>DB: 4. SELECT * FROM evaluation_results WHERE document_id = fileId
|
||
DB-->>getReviewPoints: evaluation_results (N条) ⭐
|
||
|
||
Note over getReviewPoints: 提取 evaluation_point_id 列表
|
||
|
||
getReviewPoints->>DB: 5. SELECT * FROM evaluation_points WHERE id IN (...)
|
||
DB-->>getReviewPoints: evaluation_points (N条)
|
||
|
||
Note over getReviewPoints: 提取 evaluation_point_groups_id 列表
|
||
|
||
getReviewPoints->>DB: 6. SELECT * FROM evaluation_point_groups WHERE id IN (...)
|
||
DB-->>getReviewPoints: evaluation_point_groups (M条)
|
||
|
||
Note over getReviewPoints: 筛选 post_action='manual' 的评查点
|
||
|
||
getReviewPoints->>DB: 7. SELECT * FROM audit_status WHERE document_id = fileId AND evaluation_point_id IN (...)
|
||
DB-->>getReviewPoints: audit_status (K条)
|
||
|
||
getReviewPoints->>DB: 8. SELECT * FROM cross_scoring_proposals WHERE document_id = fileId AND deleted_at IS NULL
|
||
DB-->>getReviewPoints: scoring_proposals (P条)
|
||
|
||
Note over getReviewPoints: 构建 Map 映射<br/>pointsMap, groupsMap, editAuditStatusMap
|
||
|
||
Note over getReviewPoints: 遍历 evaluation_results<br/>合并数据,提取页码
|
||
|
||
Note over getReviewPoints: 计算统计数据 stats
|
||
|
||
Note over getReviewPoints: 构建评查信息 reviewInfo
|
||
|
||
getReviewPoints-->>Client: 返回完整数据
|
||
```
|
||
|
||
---
|
||
|
||
## 💾 数据库表结构参考
|
||
|
||
### evaluation_results 表(核心表)
|
||
|
||
```sql
|
||
CREATE TABLE evaluation_results (
|
||
id SERIAL PRIMARY KEY,
|
||
document_id INTEGER NOT NULL,
|
||
evaluation_point_id INTEGER NOT NULL,
|
||
evaluated_results JSONB, -- { result, message, data }
|
||
evaluated_point_results_log JSONB, -- { rules: [...] }
|
||
final_score NUMERIC(5,2),
|
||
machine_score NUMERIC(5,2),
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW(),
|
||
FOREIGN KEY (document_id) REFERENCES documents(id),
|
||
FOREIGN KEY (evaluation_point_id) REFERENCES evaluation_points(id)
|
||
);
|
||
```
|
||
|
||
### evaluation_points 表
|
||
|
||
```sql
|
||
CREATE TABLE evaluation_points (
|
||
id SERIAL PRIMARY KEY,
|
||
evaluation_point_groups_id INTEGER NOT NULL,
|
||
name VARCHAR(255) NOT NULL,
|
||
suggestion_message_type VARCHAR(50), -- success/warning/error
|
||
suggestion_message TEXT,
|
||
score NUMERIC(5,2),
|
||
post_action VARCHAR(50), -- manual/auto
|
||
action_config JSONB,
|
||
references_laws JSONB,
|
||
evaluation_config JSONB,
|
||
fail_message TEXT,
|
||
pass_message TEXT,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW(),
|
||
FOREIGN KEY (evaluation_point_groups_id) REFERENCES evaluation_point_groups(id)
|
||
);
|
||
```
|
||
|
||
### audit_status 表
|
||
|
||
```sql
|
||
CREATE TABLE audit_status (
|
||
id SERIAL PRIMARY KEY,
|
||
document_id INTEGER NOT NULL,
|
||
evaluation_point_id INTEGER NOT NULL,
|
||
evaluation_result_id INTEGER NOT NULL,
|
||
edit_audit_status INTEGER DEFAULT 0, -- 0-待审核, 1-已审核
|
||
message TEXT,
|
||
user_id INTEGER,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW(),
|
||
FOREIGN KEY (document_id) REFERENCES documents(id),
|
||
FOREIGN KEY (evaluation_point_id) REFERENCES evaluation_points(id),
|
||
FOREIGN KEY (evaluation_result_id) REFERENCES evaluation_results(id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
**最后更新**:2025-11-26
|
||
**文档版本**:v2.0
|
||
**新增内容**:完整返回数据结构、数据依赖关系、查询时序图、数据库表结构参考
|