feat(ssa): Complete Phase I-IV intelligent dialogue and tool system development

Phase I - Session Blackboard + READ Layer:
- SessionBlackboardService with Postgres-Only cache
- DataProfileService for data overview generation
- PicoInferenceService for LLM-driven PICO extraction
- Frontend DataContextCard and VariableDictionaryPanel
- E2E tests: 31/31 passed

Phase II - Conversation Layer LLM + Intent Router:
- ConversationService with SSE streaming
- IntentRouterService (rule-first + LLM fallback, 6 intents)
- SystemPromptService with 6-segment dynamic assembly
- TokenTruncationService for context management
- ChatHandlerService as unified chat entry
- Frontend SSAChatPane and useSSAChat hook
- E2E tests: 38/38 passed

Phase III - Method Consultation + AskUser Standardization:
- ToolRegistryService with Repository Pattern
- MethodConsultService with DecisionTable + LLM enhancement
- AskUserService with global interrupt handling
- Frontend AskUserCard component
- E2E tests: 13/13 passed

Phase IV - Dialogue-Driven Analysis + QPER Integration:
- ToolOrchestratorService (plan/execute/report)
- analysis_plan SSE event for WorkflowPlan transmission
- Dual-channel confirmation (ask_user card + workspace button)
- PICO as optional hint for LLM parsing
- E2E tests: 25/25 passed

R Statistics Service:
- 5 new R tools: anova_one, baseline_table, fisher, linear_reg, wilcoxon
- Enhanced guardrails and block helpers
- Comprehensive test suite (run_all_tools_test.js)

Documentation:
- Updated system status document (v5.9)
- Updated SSA module status and development plan (v1.8)

Total E2E: 107/107 passed (Phase I: 31, Phase II: 38, Phase III: 13, Phase IV: 25)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-22 18:53:39 +08:00
parent bf10dec4c8
commit 3446909ff7
68 changed files with 11583 additions and 412 deletions

View File

@@ -349,6 +349,66 @@ export class DataProfileService {
return lines.join('\n');
}
/**
* Phase I: 获取单变量详细分析(调用 Python variable-detail 端点)
*
* @param sessionId SSA 会话 ID
* @param variableName 目标变量名
*/
async getVariableDetail(sessionId: string, variableName: string): Promise<any> {
try {
const csvContent = await this.loadCSVFromSession(sessionId);
if (!csvContent) {
return { success: false, error: 'No CSV data available for session' };
}
const response = await this.client.post('/api/ssa/variable-detail', {
csv_content: csvContent,
variable_name: variableName,
max_bins: 30,
max_qq_points: 200,
});
return response.data;
} catch (error: any) {
logger.error('[SSA:DataProfile] Variable detail failed', {
sessionId, variableName, error: error.message,
});
return { success: false, error: error.message };
}
}
/**
* 从 Session 加载原始 CSV 字符串(供 variable-detail 复用)
*/
private async loadCSVFromSession(sessionId: string): Promise<string | null> {
const session = await prisma.ssaSession.findUnique({ where: { id: sessionId } });
if (!session) return null;
if (session.dataOssKey) {
const buffer = await storage.download(session.dataOssKey);
return buffer.toString('utf-8');
}
if (session.dataPayload) {
const rows = session.dataPayload as unknown as Record<string, any>[];
if (rows.length === 0) return null;
const cols = Object.keys(rows[0]);
const lines = [cols.join(',')];
for (const row of rows) {
lines.push(cols.map(c => {
const v = row[c];
if (v === null || v === undefined) return '';
const s = String(v);
return s.includes(',') || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
}).join(','));
}
return lines.join('\n');
}
return null;
}
}
// 单例导出