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
This commit is contained in:
@@ -228,3 +228,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,4 @@ WHERE table_schema = 'dc_schema'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ ADD COLUMN IF NOT EXISTS "column_mapping" JSONB;
|
||||
-- 添加注释
|
||||
COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名映射:[{originalName, safeName, displayName}] 解决特殊字符问题';
|
||||
|
||||
|
||||
|
||||
@@ -35,3 +35,4 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,3 +185,4 @@ function extractCodeBlocks(obj, blocks = []) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -204,3 +204,4 @@ checkDCTables();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -156,3 +156,4 @@ createAiHistoryTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -143,3 +143,4 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,3 +140,4 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -308,3 +308,4 @@ runTests().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -249,3 +249,4 @@ runTest()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -287,3 +287,4 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -366,3 +366,4 @@ export class ExcelExporter {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -223,3 +223,4 @@ export const conflictDetectionService = new ConflictDetectionService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -251,3 +251,4 @@ export const templateService = new TemplateService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -173,3 +173,4 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -227,3 +227,4 @@ export const streamAIController = new StreamAIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ Write-Host "✅ 完成!" -ForegroundColor Green
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -318,3 +318,4 @@ runAdvancedTests().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -384,3 +384,4 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -342,3 +342,4 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1248,3 +1248,4 @@ interface FulltextScreeningResult {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -362,3 +362,4 @@ GET /api/v1/asl/fulltext-screening/tasks/:taskId/export
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -305,3 +305,4 @@ Linter错误:0个
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -464,3 +464,4 @@ Failed to open file '\\tmp\\extraction_service\\temp_10000_test.pdf'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -530,3 +530,4 @@ df['creatinine'] = pd.to_numeric(df['creatinine'], errors='coerce')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -368,3 +368,4 @@ npm run dev
|
||||
**修复完成时间**: 2025-12-08 当前时间
|
||||
**状态**: ✅ 已完成,待测试验证
|
||||
|
||||
|
||||
|
||||
@@ -945,3 +945,4 @@ export const aiController = new AIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1279,3 +1279,4 @@ npm install react-markdown
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -301,3 +301,4 @@ Changes:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -373,3 +373,4 @@ cd path; command
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -602,3 +602,4 @@ import { logger } from '../../../../common/logging/index.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -606,3 +606,4 @@ Content-Length: 45234
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -258,3 +258,4 @@ Response:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -411,3 +411,4 @@ Response:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -405,3 +405,4 @@ import { ChatContainer } from '@/shared/components/Chat';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -315,3 +315,4 @@ const initialMessages = defaultMessages.length > 0 ? defaultMessages : [{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -355,3 +355,4 @@ python main.py
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -603,3 +603,4 @@ http://localhost:5173/data-cleaning/tool-c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -213,3 +213,4 @@ Day 5 (6-8小时):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -391,3 +391,4 @@ Docs: docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -366,3 +366,4 @@ const mockAssets: Asset[] = [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -350,3 +350,4 @@ frontend-v2/src/modules/dc/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -310,3 +310,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -264,3 +264,4 @@ ConflictDetectionService // 冲突检测(字段级对比)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -313,3 +313,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -276,3 +276,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -340,3 +340,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -428,3 +428,4 @@ Tool B后端代码**100%复用**了平台通用能力层,无任何重复开发
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -274,3 +274,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -205,3 +205,4 @@ $ node scripts/check-dc-tables.mjs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -438,3 +438,4 @@ ${fields.map((f, i) => `${i + 1}. ${f.name}:${f.desc}`).join('\n')}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -452,3 +452,4 @@ import { ChatContainer } from '@/shared/components/Chat';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -851,15 +851,22 @@ async def operation_binning(request: BinningRequest):
|
||||
request.num_bins
|
||||
)
|
||||
|
||||
# 转换回JSON(处理Categorical类型和NaN值)
|
||||
# 转换回JSON(处理Categorical类型、NaN值和inf值)
|
||||
import numpy as np
|
||||
|
||||
# 1. 将Categorical列转为字符串
|
||||
for col in result_df.columns:
|
||||
if pd.api.types.is_categorical_dtype(result_df[col]):
|
||||
result_df[col] = result_df[col].astype(str)
|
||||
|
||||
# 2. 将NaN替换为None(避免JSON序列化错误)
|
||||
result_df_clean = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
result_data = result_df_clean.to_dict('records')
|
||||
# 2. 替换inf和-inf为None
|
||||
result_df = result_df.replace([np.inf, -np.inf], None)
|
||||
|
||||
# 3. 将NaN替换为None(避免JSON序列化错误)
|
||||
result_df = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
|
||||
# 4. 转换为dict
|
||||
result_data = result_df.to_dict('records')
|
||||
|
||||
# 恢复stdout
|
||||
sys.stdout = sys.__stdout__
|
||||
@@ -869,13 +876,22 @@ async def operation_binning(request: BinningRequest):
|
||||
|
||||
logger.info(f"分箱成功: {request.column} → {request.new_column_name}")
|
||||
|
||||
return JSONResponse(content={
|
||||
# 使用json.dumps手动序列化(确保NaN完全处理)
|
||||
import json
|
||||
response_content = {
|
||||
"success": True,
|
||||
"result_data": result_data,
|
||||
"output": output,
|
||||
"execution_time": execution_time,
|
||||
"result_shape": [len(result_data), len(result_df.columns)]
|
||||
})
|
||||
}
|
||||
|
||||
# 手动序列化,NaN会被转为null
|
||||
json_str = json.dumps(response_content, allow_nan=True)
|
||||
# 替换NaN为null(防止任何遗漏的NaN)
|
||||
json_str = json_str.replace('NaN', 'null').replace('Infinity', 'null').replace('-Infinity', 'null')
|
||||
|
||||
return JSONResponse(content=json.loads(json_str))
|
||||
|
||||
except Exception as e:
|
||||
sys.stdout = sys.__stdout__
|
||||
@@ -937,9 +953,11 @@ async def operation_conditional(request: ConditionalRequest):
|
||||
request.else_value
|
||||
)
|
||||
|
||||
# 转换回JSON(处理NaN值)
|
||||
result_df_clean = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
result_data = result_df_clean.to_dict('records')
|
||||
# 转换回JSON(处理NaN和inf值)
|
||||
import numpy as np
|
||||
result_df = result_df.replace([np.inf, -np.inf], None)
|
||||
result_df = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
result_data = result_df.to_dict('records')
|
||||
|
||||
# 恢复stdout
|
||||
sys.stdout = sys.__stdout__
|
||||
@@ -1015,9 +1033,11 @@ async def operation_dropna(request: DropnaRequest):
|
||||
subset=request.subset
|
||||
)
|
||||
|
||||
# 转换回JSON(处理NaN值)
|
||||
result_df_clean = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
result_data = result_df_clean.to_dict('records')
|
||||
# 转换回JSON(处理NaN和inf值)
|
||||
import numpy as np
|
||||
result_df = result_df.replace([np.inf, -np.inf], None)
|
||||
result_df = result_df.fillna(value=pd.NA).replace({pd.NA: None})
|
||||
result_data = result_df.to_dict('records')
|
||||
|
||||
# 恢复stdout
|
||||
sys.stdout = sys.__stdout__
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
__version__ = '1.0.0'
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ def validate_formula(formula: str, available_columns: list) -> tuple[bool, str]:
|
||||
return False, f'公式包含不允许的操作: {pattern}'
|
||||
|
||||
# ✨ 增强:检查是否只包含允许的字符(放宽限制,支持更多特殊字符)
|
||||
# 允许:英文字母、数字、下划线、中文、空格、运算符、括号(中英文)、逗号、点、冒号、等号
|
||||
allowed_chars = r'[a-zA-Z0-9_\u4e00-\u9fa5\s\+\-\*/\(\)\[\]\{\}\.,:\*\*=()【】、。:;!?]'
|
||||
# 允许:英文字母、数字、下划线、中文、空格、运算符、括号(中英文)、逗号(中英文)、点、冒号、等号
|
||||
allowed_chars = r'[a-zA-Z0-9_\u4e00-\u9fa5\s\+\-\*/\(\)\[\]\{\}\.,,:\*\*=()【】、。:;!?]'
|
||||
if not re.match(f'^{allowed_chars}+$', formula):
|
||||
# 找出不允许的字符
|
||||
invalid_chars = set(re.findall(f'[^{allowed_chars}]', formula))
|
||||
|
||||
@@ -148,3 +148,4 @@ def get_missing_summary(df: pd.DataFrame) -> dict:
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -108,3 +108,4 @@ def apply_filter(
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -282,3 +282,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,3 +48,4 @@ except Exception as e:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,3 +28,4 @@ except Exception as e:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -517,3 +517,4 @@ export default FulltextDetailDrawer;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -116,3 +116,4 @@ export function useFulltextResults({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -79,3 +79,4 @@ export function useFulltextTask({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -470,3 +470,4 @@ export default FulltextResults;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -110,3 +110,4 @@ export const useAssets = (activeTab: AssetTabType) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -100,3 +100,4 @@ export const useRecentTasks = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -336,3 +336,4 @@ const BinningDialog: React.FC<BinningDialogProps> = ({
|
||||
export default BinningDialog;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -299,3 +299,4 @@ const DropnaDialog: React.FC<Props> = ({
|
||||
export default DropnaDialog;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -261,3 +261,4 @@ const PivotDialog: React.FC<Props> = ({
|
||||
export default PivotDialog;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -62,3 +62,4 @@ export interface DataStats {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -58,3 +58,4 @@ export type AssetTabType = 'all' | 'processed' | 'raw';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ export { default as Placeholder } from './Placeholder';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
__version__ = '1.0.0'
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,3 +122,4 @@ def apply_binning(
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -108,3 +108,4 @@ def apply_filter(
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -78,3 +78,4 @@ def apply_recode(
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -222,3 +222,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,3 +46,4 @@ Write-Host "====================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user