Files
AIclinicalresearch/frontend-v2/src/modules/dc/pages/tool-c/components/PivotDialog.tsx
HaHafeng 75ceeb0653 hotfix(dc/tool-c): Fix compute formula validation and binning NaN serialization
Critical fixes:
1. Compute column: Add Chinese comma support in formula validation
   - Problem: Formula with Chinese comma failed validation
   - Fix: Add Chinese comma character to allowed_chars regex
   - Example: Support formulas like 'col1(kg)+ col2,col3'

2. Binning operation: Fix NaN serialization error
   - Problem: 'Out of range float values are not JSON compliant: nan'
   - Fix: Enhanced NaN/inf handling in binning endpoint
   - Added np.inf/-np.inf replacement before JSON serialization
   - Added manual JSON serialization with NaN->null conversion

3. Enhanced all operation endpoints for consistency
   - Updated conditional, dropna endpoints with same NaN/inf handling
   - Ensures all operations return JSON-compliant data

Modified files:
- extraction_service/operations/compute.py: Add Chinese comma to regex
- extraction_service/main.py: Enhanced NaN handling in binning/conditional/dropna

Status: Hotfix complete, ready for testing
2025-12-09 08:45:27 +08:00

265 lines
8.1 KiB
TypeScript
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.
import React, { useState, useEffect } from 'react';
import { Modal, Select, Button, Alert, Checkbox, Radio, App, Tag } from 'antd';
import { ArrowLeftRight, Info } from 'lucide-react';
interface Props {
visible: boolean;
onClose: () => void;
onApply: (newData: any[]) => void;
columns: Array<{ id: string; name: string }>;
sessionId: string | null;
}
const PivotDialog: React.FC<Props> = ({
visible,
onClose,
onApply,
columns,
sessionId,
}) => {
const { message } = App.useApp();
const [indexColumn, setIndexColumn] = useState<string>('');
const [pivotColumn, setPivotColumn] = useState<string>('');
const [valueColumns, setValueColumns] = useState<string[]>([]);
const [aggfunc, setAggfunc] = useState<'first' | 'last' | 'mean' | 'sum'>('first');
const [loading, setLoading] = useState(false);
// 重置状态
useEffect(() => {
if (visible) {
setIndexColumn('');
setPivotColumn('');
setValueColumns([]);
setAggfunc('first');
}
}, [visible]);
// 执行
const handleApply = async () => {
if (!sessionId) {
message.error('Session ID不存在');
return;
}
// 验证
if (!indexColumn) {
message.warning('请选择索引列(唯一标识列)');
return;
}
if (!pivotColumn) {
message.warning('请选择透视列(要变成列名的列)');
return;
}
if (valueColumns.length === 0) {
message.warning('请至少选择一个值列');
return;
}
setLoading(true);
try {
const response = await fetch('/api/v1/dc/tool-c/quick-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId,
action: 'pivot',
params: {
indexColumn,
pivotColumn,
valueColumns,
aggfunc,
},
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Pivot转换失败');
}
message.success('Pivot转换成功');
// 更新父组件数据
if (result.data?.newDataPreview) {
onApply(result.data.newDataPreview);
}
// 成功后关闭
onClose();
} catch (error: any) {
message.error(error.message || '执行失败');
} finally {
setLoading(false);
}
};
return (
<Modal
title={
<div className="flex items-center gap-2">
<ArrowLeftRight size={20} className="text-purple-500" />
<span>Pivot</span>
</div>
}
open={visible}
onCancel={onClose}
width={700}
footer={[
<Button key="cancel" onClick={onClose}>
</Button>,
<Button
key="apply"
type="primary"
onClick={handleApply}
loading={loading}
icon={<ArrowLeftRight size={16} />}
>
</Button>,
]}
>
<div className="space-y-4">
{/* 说明 */}
<Alert
title="功能说明"
description={
<div className="text-xs space-y-1">
<div> "一人多行""一人一行"</div>
<div> 访线访2访1</div>
<div> Event_Name列的"基线""随访2周" FMA_基线FMA_随访2周</div>
</div>
}
type="info"
showIcon
icon={<Info size={16} />}
className="mb-4"
/>
{/* 索引列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
ID<span className="text-red-500">*</span>
</label>
<Select
placeholder="选择唯一标识列"
value={indexColumn || undefined}
onChange={setIndexColumn}
className="w-full"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={columns.map(col => ({ label: col.name, value: col.id }))}
/>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
{/* 透视列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
访<span className="text-red-500">*</span>
</label>
<Select
placeholder="选择透视列"
value={pivotColumn || undefined}
onChange={setPivotColumn}
className="w-full"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={columns
.filter(col => col.id !== indexColumn)
.map(col => ({ label: col.name, value: col.id }))}
/>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
{/* 值列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
<span className="text-red-500">*</span>
</label>
<div className="border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50">
<Checkbox.Group
value={valueColumns}
onChange={setValueColumns}
className="flex flex-col gap-2"
>
{columns
.filter(col => col.id !== indexColumn && col.id !== pivotColumn)
.map((col) => (
<Checkbox key={col.id} value={col.id}>
{col.name}
</Checkbox>
))}
</Checkbox.Group>
</div>
<div className="text-xs text-slate-500 mt-1">
FMA_基线FMA_随访2周
</div>
</div>
{/* 聚合方式 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<Radio.Group value={aggfunc} onChange={(e) => setAggfunc(e.target.value)}>
<div className="space-y-2">
<Radio value="first">
<div className="ml-2">
<span className="font-medium"></span>
<span className="text-xs text-slate-500 ml-2"></span>
</div>
</Radio>
<Radio value="last">
<span className="ml-2 font-medium"></span>
</Radio>
<Radio value="mean">
<span className="ml-2 font-medium"></span>
</Radio>
<Radio value="sum">
<span className="ml-2 font-medium"></span>
</Radio>
</div>
</Radio.Group>
<div className="text-xs text-slate-500 mt-2">
+
</div>
</div>
{/* 警告 */}
<Alert
title="重要提示"
description={
<div className="text-xs space-y-1">
<div> Pivot操作会显著改变数据结构</div>
<div> </div>
<div> AI对话探索数据结构</div>
<div> ID</div>
</div>
}
type="warning"
showIcon={false}
className="bg-orange-50 border-orange-200"
/>
</div>
</Modal>
);
};
export default PivotDialog;