Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md
HaHafeng 57fdc6ef00 feat(aia): Integrate PromptService for 10 AI agents
Features:
- Migrate 10 agent prompts from hardcoded to database
- Add grayscale preview support (DRAFT/ACTIVE distribution)
- Implement 3-tier fallback (DB -> Cache -> Hardcoded)
- Add version management and rollback capability

Files changed:
- backend/scripts/migrate-aia-prompts.ts (new migration script)
- backend/src/common/prompt/prompt.fallbacks.ts (add AIA fallbacks)
- backend/src/modules/aia/services/agentService.ts (integrate PromptService)
- backend/src/modules/aia/services/conversationService.ts (pass userId)
- backend/src/modules/aia/types/index.ts (fix AgentStage type)

Documentation:
- docs/03-业务模块/AIA-AI智能问答/06-开发记录/2026-01-18-Prompt管理系统集成.md
- docs/02-通用能力层/00-通用能力层清单.md (add FileCard, Prompt management)
- docs/00-系统总体设计/00-系统当前状态与开发指南.md (update to v3.6)

Prompt codes:
- AIA_SCIENTIFIC_QUESTION, AIA_PICO_ANALYSIS, AIA_TOPIC_EVALUATION
- AIA_OUTCOME_DESIGN, AIA_CRF_DESIGN, AIA_SAMPLE_SIZE
- AIA_PROTOCOL_WRITING, AIA_METHODOLOGY_REVIEW
- AIA_PAPER_POLISH, AIA_PAPER_TRANSLATE

Tested: Migration script executed, all 10 prompts inserted successfully
2026-01-18 15:48:53 +08:00

400 lines
8.5 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 - 方案B实施总结列名特殊字符解决方案
> **日期**: 2025-12-09
> **版本**: v1.0
> **实施方案**: 方案B - Python负责列名替换
---
## 📋 问题背景
### 原始问题
用户上传的Excel文件表头包含特殊字符导致计算列功能失败
**示例表头**:
- `体重kg`
- `1.高血压病(无=0有=1不知道=2`
- `身高cm`
**报错信息**:
```
计算列失败:公式验证失败: 公式包含不允许的字符
```
---
## 🎯 方案选择
### 方案对比
| 方案 | 描述 | 优点 | 缺点 | 评分 |
|------|------|------|------|------|
| **方案A** | 用户使用序号引用col_0, col_1 | 技术最安全 | 用户体验差,不直观 | ⭐⭐ |
| **方案B** | 用户使用原列名Python负责替换 | 用户体验好,技术可靠 | 需实现替换逻辑 | ⭐⭐⭐⭐⭐ |
| **方案C** | 前端替换列名 | 减少网络传输 | 边界识别困难,不可靠 | ⭐⭐⭐ |
**最终选择**: **方案B**
---
## 🏗️ 架构设计
### 数据流
```
用户输入公式(原列名)
前端体重kg / (身高cm/100)**2
后端:获取 columnMapping
传递给Python: {
formula: "体重kg / (身高cm/100)**2",
column_mapping: [
{"originalName": "体重kg", "safeName": "col_0"},
{"originalName": "身高cm", "safeName": "col_1"}
]
}
Python替换: col_0 / (col_1/100)**2
执行计算 ✅
```
### 职责划分
| 层级 | 职责 | 关键点 |
|------|------|--------|
| **前端** | UI交互、数据收集 | 用户看到和输入原列名 |
| **后端** | 获取columnMapping、传递给Python | 从Session获取映射 |
| **Python** | 列名替换、公式执行 | 按长度排序、精确替换 |
---
## 💻 实施细节
### 1. 前端ComputeDialog.tsx
**保持不变** - 已经使用原列名方式
```typescript
// 用户点击列名标签,插入到公式框
<Tag onClick={() => setFormula(formula + col.name)}>
{col.name} {/* 显示原列名体重kg */}
</Tag>
// 提交时直接传递原公式
onApply({
newColumnName: "BMI",
formula: "体重kg / (身高cm/100)**2", // 原列名
});
```
### 2. 后端QuickActionController.ts
**修改**: 获取session并传递columnMapping
```typescript
// 获取session包含columnMapping
session = await sessionService.getSession(sessionId);
// 传递给QuickActionService
executeResult = await quickActionService.executeCompute(
fullData,
params,
session.columnMapping // ✅ 传递映射
);
```
### 3. 后端QuickActionService.ts
**修改**: 接收并传递columnMapping给Python
```typescript
async executeCompute(
data: any[],
params: ComputeParams,
columnMapping?: any[] // ✅ 新增参数
): Promise<OperationResult> {
const response = await axios.post(`${PYTHON_SERVICE_URL}/api/operations/compute`, {
data,
new_column_name: params.newColumnName,
formula: params.formula,
column_mapping: columnMapping || [], // ✅ 传递映射
});
return response.data;
}
```
### 4. Pythonmain.py
**修改**: 更新请求模型
```python
class ComputeRequest(BaseModel):
data: List[Dict[str, Any]]
new_column_name: str
formula: str
column_mapping: List[Dict[str, str]] = [] # ✅ 新增字段
@app.post("/api/operations/compute")
async def operation_compute(request: ComputeRequest):
result_df = compute_column(
df,
request.new_column_name,
request.formula,
request.column_mapping # ✅ 传递映射
)
```
### 5. Pythoncompute.py
**核心实现**: 列名替换逻辑
```python
def replace_column_names_in_formula(
formula: str,
column_mapping: List[Dict[str, str]]
) -> str:
"""
✅ 核心算法:可靠的列名替换
"""
safe_formula = formula
# 关键1按列名长度倒序排序
# 避免子串问题:先替换"高血压病史",再替换"高血压"
sorted_mapping = sorted(
column_mapping,
key=lambda x: len(x['originalName']),
reverse=True
)
# 关键2逐个精确替换不使用正则
for item in sorted_mapping:
original = item['originalName']
safe = item['safeName']
if original in safe_formula:
safe_formula = safe_formula.replace(original, safe)
return safe_formula
def compute_column(
df: pd.DataFrame,
new_column_name: str,
formula: str,
column_mapping: Optional[List[Dict[str, str]]] = None
) -> pd.DataFrame:
"""
✅ 方案BPython负责替换
"""
# 1. 替换列名
if column_mapping:
safe_formula = replace_column_names_in_formula(formula, column_mapping)
else:
safe_formula = formula
# 2. 准备执行环境
env = {}
for item in column_mapping:
env[item['safeName']] = df[item['originalName']]
env.update(ALLOWED_FUNCTIONS)
# 3. 执行(不需要字符验证!)
result = eval(safe_formula, {"__builtins__": {}}, env)
return df.assign(**{new_column_name: result})
```
---
## ✅ 解决的问题
### 1. 特殊字符问题 ✅
- **问题**: `体重kg` 包含中文括号
- **解决**: Python使用安全列名 `col_0`,不受特殊字符影响
### 2. 子串包含问题 ✅
- **问题**: "高血压" 和 "高血压病史" 可能误替换
- **解决**: 按长度倒序排序,先替换长列名
### 3. 边界识别问题 ✅
- **问题**: 正则`\b`对中文字符不可靠
- **解决**: 使用Python字符串`replace`,简单可靠
### 4. 字符白名单问题 ✅
- **问题**: 需要枚举所有允许的字符
- **解决**: 不需要验证Python只处理安全列名
---
## 🧪 测试用例
### 测试1基本功能
```python
column_mapping = [
{"originalName": "体重kg", "safeName": "col_0"},
{"originalName": "身高cm", "safeName": "col_1"}
]
formula = "体重kg / (身高cm/100)**2"
# 预期: col_0 / (col_1/100)**2 ✅
```
### 测试2子串包含
```python
column_mapping = [
{"originalName": "高血压", "safeName": "col_0"},
{"originalName": "高血压病史", "safeName": "col_1"}
]
formula = "高血压病史 + 高血压"
# 预期: col_1 + col_0 ✅(因为按长度排序)
```
### 测试3复杂特殊字符
```python
column_mapping = [
{"originalName": "1.高血压病(无=0有=1不知道=2", "safeName": "col_0"}
]
formula = "1.高血压病(无=0有=1不知道=2 * 2"
# 预期: col_0 * 2 ✅
```
### 测试4嵌套括号
```python
column_mapping = [
{"originalName": "FMA总分0-100", "safeName": "col_0"}
]
formula = "FMA总分0-100 / 100"
# 预期: col_0 / 100 ✅
```
---
## 📊 性能影响
| 指标 | 影响 | 说明 |
|------|------|------|
| **网络传输** | +5KB | columnMapping约5KB100列 |
| **替换时间** | <1ms | 字符串替换非常快 |
| **总体性能** | 可忽略 | 相比数据处理时间(秒级)可忽略 |
---
## 🎯 优势总结
### 用户体验 ⭐⭐⭐⭐⭐
- ✅ 用户看到和输入原列名
- ✅ 公式直观易懂
- ✅ 历史记录清晰
### 技术可靠性 ⭐⭐⭐⭐⭐
- ✅ 不依赖正则边界识别
- ✅ 按长度排序避免子串问题
- ✅ Python字符串操作简单可靠
### 可维护性 ⭐⭐⭐⭐⭐
- ✅ 职责清晰前端UI、Python逻辑
- ✅ 易于调试(可打印替换日志)
- ✅ 未来不会再有字符问题
---
## 📝 后续工作
### 已完成 ✅
- [x] 前端保持使用原列名
- [x] 后端传递columnMapping
- [x] Python实现替换逻辑
- [x] 移除字符验证
- [x] 更新Pivot操作
### 待测试 ⏳
- [ ] 用户实际测试
- [ ] 边界情况验证
- [ ] 性能测试
### 未来优化 💡
- [ ] 添加公式语法高亮
- [ ] 列名自动补全
- [ ] 公式错误提示优化
---
## 🔗 相关文件
### 修改的文件
1. `backend/src/modules/dc/tool-c/controllers/QuickActionController.ts`
2. `backend/src/modules/dc/tool-c/services/QuickActionService.ts`
3. `extraction_service/main.py`
4. `extraction_service/operations/compute.py`
5. `extraction_service/operations/pivot.py`
### 文档
- 本文档:`工具C_方案B实施总结_2025-12-09.md`
- 原Bug报告`工具C_Bug修复总结_2025-12-08.md`
---
## ✨ 总结
方案B成功实现了
1. **用户体验优秀** - 使用原列名,直观易懂
2. **技术可靠** - Python替换简单可控
3. **彻底解决** - 不再有特殊字符问题
**下一步**: 等待用户测试验证 ✅