Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md
HaHafeng bdfca32305 docs(iit): REDCap对接技术方案完成与模块状态更新
- 新增《REDCap对接技术方案与实施指南》(1070行)
  - 确定DET+REST API技术方案(不使用External Module)
  - 完整RedcapAdapter/WebhookController/SyncManager代码设计
  - Day 2详细实施步骤与验收标准
- 更新《IIT Manager Agent模块当前状态与开发指南》
  - 记录REDCap本地环境部署完成(15.8.0)
  - 记录对接方案确定过程与技术决策
  - 更新Day 2工作计划(6个阶段详细清单)
  - 整体进度18%(Day 1完成+REDCap环境就绪)
- REDCap环境准备完成
  - 测试项目test0102(PID 16)创建成功
  - DET功能源码验证通过
  - 本地Docker环境稳定运行

技术方案:
- 实时触发: Data Entry Trigger (0秒延迟)
- 数据拉取: REST API exportRecords (增量同步)
- 轮询补充: pg-boss定时任务 (每30分钟)
- 可靠性: Webhook幂等性 + 轮询补充机制
2026-01-02 14:30:38 +08:00

220 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工具C - Pivot列顺序优化总结
## 📋 问题描述
**用户需求**:长宽转换后,列的排序应该与上传文件时的列顺序保持一致。
**当前问题**:系统按字母顺序排列转换后的列,导致顺序与原文件不一致。
---
## 🎯 解决方案方案A - Python端排序
### 核心思路
1. Node.js后端从session获取**原始列顺序**
2. Node.js后端从数据中提取**透视列值的原始顺序**(按首次出现顺序)
3. 传递给Python
4. Python在pivot后按原始顺序重排列
---
## 🛠️ 实现细节
### 1. Python端pivot.py
**新增参数**
- `original_column_order: List[str]`:原始列顺序(如`['Record ID', 'Event Name', 'FMA', '体重', '收缩压', ...]`
- `pivot_value_order: List[str]`:透视列值的原始顺序(如`['基线', '1个月', '2个月', ...]`
**排序逻辑**
```python
if original_column_order:
# 1. 索引列始终在最前面
final_cols = [index_column]
# 2. 按原始列顺序添加转换后的列
for orig_col in original_column_order:
if orig_col in value_columns:
# 找出所有属于这个原列的新列
related_cols = [c for c in df_pivot.columns if c.startswith(f'{orig_col}___')]
# ✨ 按透视列的原始顺序排序
if pivot_value_order:
pivot_order_map = {val: idx for idx, val in enumerate(pivot_value_order)}
related_cols_sorted = sorted(
related_cols,
key=lambda c: pivot_order_map.get(c.split('___')[1], 999)
)
else:
related_cols_sorted = sorted(related_cols)
final_cols.extend(related_cols_sorted)
# 3. 添加未选择的列(保持原始顺序)
if keep_unused_columns:
for orig_col in original_column_order:
if orig_col in df_pivot.columns and orig_col not in final_cols:
final_cols.append(orig_col)
# 4. 重排列
df_pivot = df_pivot[final_cols]
```
### 2. Python端main.py
**PivotRequest模型**
```python
class PivotRequest(BaseModel):
# ... 原有字段 ...
original_column_order: List[str] = [] # ✨ 新增
pivot_value_order: List[str] = [] # ✨ 新增
```
**调用pivot_long_to_wide**
```python
result_df = pivot_long_to_wide(
df,
request.index_column,
request.pivot_column,
request.value_columns,
request.aggfunc,
request.column_mapping,
request.keep_unused_columns,
request.unused_agg_method,
request.original_column_order, # ✨ 新增
request.pivot_value_order # ✨ 新增
)
```
### 3. Node.js后端QuickActionController.ts
**获取原始列顺序**
```typescript
const originalColumnOrder = session.columns || [];
```
**获取透视列值的原始顺序**
```typescript
const pivotColumn = params.pivotColumn;
const seenPivotValues = new Set();
const pivotValueOrder: string[] = [];
for (const row of fullData) {
const pivotValue = row[pivotColumn];
if (pivotValue !== null && pivotValue !== undefined && !seenPivotValues.has(pivotValue)) {
seenPivotValues.add(pivotValue);
pivotValueOrder.push(String(pivotValue));
}
}
```
**传递给QuickActionService**
```typescript
executeResult = await quickActionService.executePivot(
fullData,
params,
session.columnMapping,
originalColumnOrder, // ✨ 新增
pivotValueOrder // ✨ 新增
);
```
### 4. Node.js后端QuickActionService.ts
**方法签名**
```typescript
async executePivot(
data: any[],
params: PivotParams,
columnMapping?: any[],
originalColumnOrder?: string[], // ✨ 新增
pivotValueOrder?: string[] // ✨ 新增
): Promise<OperationResult>
```
**传递给Python**
```typescript
const response = await axios.post(`${PYTHON_SERVICE_URL}/api/operations/pivot`, {
// ... 原有参数 ...
original_column_order: originalColumnOrder || [], // ✨ 新增
pivot_value_order: pivotValueOrder || [], // ✨ 新增
});
```
---
## 📊 效果对比
### 修改前(按字母顺序)
```
Record ID | FMA___基线 | FMA___1个月 | 收缩压___基线 | 收缩压___1个月 | 体重___基线 | 体重___1个月
↑ ↑ ↑ ↑ ↑ ↑ ↑
索引列 F开头 F开头 S开头(拼音) S开头 T开头 T开头
```
### 修改后(按原始顺序)
```
Record ID | FMA___基线 | FMA___1个月 | 体重___基线 | 体重___1个月 | 收缩压___基线 | 收缩压___1个月
↑ ↑ ↑ ↑ ↑ ↑ ↑
索引列 原文件第3列 原文件第3列 原文件第4列 原文件第4列 原文件第5列 原文件第5列
```
### 透视值内部顺序(按原始出现顺序)
```
FMA___基线 | FMA___1个月 | FMA___2个月
↑ ↑ ↑
首次出现 第二次出现 第三次出现
(而不是按"1个月"、"2个月"、"基线"的字母顺序)
```
---
## ✅ 开发完成
### 修改文件清单
1.`extraction_service/operations/pivot.py`
2.`extraction_service/main.py`
3.`backend/src/modules/dc/tool-c/controllers/QuickActionController.ts`
4.`backend/src/modules/dc/tool-c/services/QuickActionService.ts`
### 优势
- ✅ 列顺序与原文件一致(用户熟悉)
- ✅ 透视值顺序按时间顺序基线→1个月→2个月
- ✅ 未选择的列也保持原始顺序
- ✅ 导出Excel时顺序正确
---
**开发时间**2025-12-09
**状态**:✅ 已完成,等待测试