fix(ssa): Fix 7 integration bugs and refactor frontend unified state management

Bug fixes:
- Fix garbled error messages in chat (TypeWriter rendering issue)
- Fix R engine NA crash in descriptive.R (defensive isTRUE/is.na checks)
- Fix intent misclassification for statistical significance queries
- Fix step 2 results not displayed (accept warning status alongside success)
- Fix incomplete R code download (only step 1 included)
- Fix multi-task state confusion (clicking old card shows new results)
- Add R engine and backend parameter logging for debugging

Refactor - Unified Record Architecture:
- Replace 12 global singleton fields with AnalysisRecord as single source of truth
- Remove isWorkflowMode branching across all components
- One Analysis = One Record = N Steps paradigm
- selectRecord only sets currentRecordId, all rendering derives from currentRecord
- Fix cross-hook-instance issue: executeWorkflow fallback to store currentRecordId

Updated files: ssaStore, useWorkflow, useAnalysis, SSAChatPane, SSAWorkspacePane,
SSACodeModal, WorkflowTimeline, QueryService, WorkflowExecutorService, descriptive.R

Tested: Manual integration test passed - multi-task switching, R code completeness
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-21 22:58:59 +08:00
parent 371e1c069c
commit 11676f2840
17 changed files with 1573 additions and 1829 deletions

View File

@@ -411,7 +411,9 @@ export class QueryService {
if (query.includes('比较') || query.includes('差异') || query.includes('不同') || query.includes('有没有效')) {
goal = 'comparison';
} else if (query.includes('相关') || query.includes('关系') || query.includes('关联')) {
} else if (query.includes('相关') || query.includes('关系') || query.includes('关联')
|| query.includes('统计学意义') || query.includes('显著') || query.includes('检验')
|| query.includes('p值') || query.includes('有无差别')) {
goal = 'correlation';
} else if (query.includes('影响') || query.includes('因素') || query.includes('预测') || query.includes('回归')) {
goal = 'regression';

View File

@@ -395,6 +395,11 @@ export class WorkflowExecutorService extends EventEmitter {
});
// 调用 R 服务
logger.info('[SSA:Executor] Calling R service', {
step: step.stepOrder,
toolCode: step.toolCode,
inputParams: step.inputParams,
});
const response = await this.rClient.post(`/api/v1/skills/${step.toolCode}`, {
data_source: dataSource,
params: step.inputParams,
@@ -410,6 +415,16 @@ export class WorkflowExecutorService extends EventEmitter {
if (response.data.status === 'error' || response.data.status === 'blocked') {
const rMsg = response.data.message || '执行失败';
const classified = classifyRError(rMsg);
logger.warn('[SSA:Executor] R tool returned error', {
step: step.stepOrder,
toolCode: step.toolCode,
rMessage: rMsg,
rErrorCode: response.data.error_code,
rUserHint: response.data.user_hint,
classifiedCode: classified.code,
});
return {
stepOrder: step.stepOrder,
toolCode: step.toolCode,

View File

@@ -74,13 +74,47 @@ export interface PrunedProfile {
// 2. LLM 原始输出的 Zod Schema静态版本
// ────────────────────────────────────────────
const VALID_VAR_TYPES = ['continuous', 'binary', 'categorical', 'ordinal', 'datetime'] as const;
const VAR_TYPE_ALIAS: Record<string, VariableType> = {
numeric: 'continuous',
integer: 'continuous',
int: 'continuous',
float: 'continuous',
double: 'continuous',
number: 'continuous',
real: 'continuous',
factor: 'categorical',
string: 'categorical',
text: 'categorical',
character: 'categorical',
char: 'categorical',
nominal: 'categorical',
boolean: 'binary',
bool: 'binary',
logical: 'binary',
dichotomous: 'binary',
date: 'datetime',
time: 'datetime',
timestamp: 'datetime',
};
function normalizeVarType(val: unknown): VariableType {
if (typeof val !== 'string') return 'continuous';
const lower = val.toLowerCase().trim();
if ((VALID_VAR_TYPES as readonly string[]).includes(lower)) return lower as VariableType;
return VAR_TYPE_ALIAS[lower] ?? 'continuous';
}
const varTypeSchema = z.preprocess(normalizeVarType, z.enum(VALID_VAR_TYPES));
/** LLM 直接输出的 JSON 结构Zod 校验用) */
export const LLMIntentOutputSchema = z.object({
goal: z.enum(['comparison', 'correlation', 'regression', 'descriptive', 'cohort_study']),
outcome_var: z.string().nullable().default(null),
outcome_type: z.enum(['continuous', 'binary', 'categorical', 'ordinal', 'datetime']).nullable().default(null),
outcome_type: varTypeSchema.nullable().default(null),
predictor_vars: z.array(z.string()).default([]),
predictor_types: z.array(z.enum(['continuous', 'binary', 'categorical', 'ordinal', 'datetime'])).default([]),
predictor_types: z.array(varTypeSchema).default([]),
grouping_var: z.string().nullable().default(null),
design: z.enum(['independent', 'paired', 'longitudinal', 'cross_sectional']).default('independent'),
confidence: z.number().min(0).max(1).default(0.5),