feat(ssa): Complete Phase V-A editable analysis plan variables

Features:
- Add editable variable selection in workflow plan (SingleVarSelect + MultiVarTags)
- Implement 3-layer flexible interception (warning bar + icon + blocking dialog)
- Add tool_param_constraints.json for 12 statistical tools parameter validation
- Add PATCH /workflow/:id/params API with Zod structural validation
- Implement synchronous parameter sync before execution (Promise chaining)
- Fix LLM hallucination by strict system prompt constraints
- Fix DynamicReport object-based rows compatibility (R baseline_table)
- Fix Word export row.map error with same normalization logic
- Restore inferGroupingVar for smart default variable selection
- Add ReactMarkdown rendering in SSAChatPane
- Update SSA module status document to v3.5

Modified files:
- backend: workflow.routes, ChatHandlerService, SystemPromptService, FlowTemplateService
- frontend: WorkflowTimeline, SSAWorkspacePane, DynamicReport, SSAChatPane, ssaStore, ssa.css
- config: tool_param_constraints.json (new)
- docs: SSA status doc, team review reports

Tested: Cohort study end-to-end execution + report export verified
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 13:08:29 +08:00
parent dc6b292308
commit 85fda830c2
27 changed files with 2732 additions and 154 deletions

View File

@@ -77,8 +77,8 @@ function blockToDocxElements(block: ReportBlock, index: number): (Paragraph | Ta
case 'table': {
const headers = block.headers ?? [];
const rows = block.rows ?? [];
if (headers.length > 0 || rows.length > 0) {
const rawRows = block.rows ?? [];
if (headers.length > 0 || (Array.isArray(rawRows) && rawRows.length > 0)) {
if (block.title) {
elements.push(
new Paragraph({
@@ -91,8 +91,15 @@ function blockToDocxElements(block: ReportBlock, index: number): (Paragraph | Ta
if (headers.length > 0) {
tableRows.push(makeRow(headers.map(String), true));
}
for (const row of rows) {
tableRows.push(makeRow(row.map(c => (c === null || c === undefined ? '-' : String(c)))));
const normalizedRows = (Array.isArray(rawRows) ? rawRows : []).map((row: any) => {
if (Array.isArray(row)) return row;
if (row && typeof row === 'object') {
return headers.length > 0 ? headers.map((h: string) => row[h] ?? '') : Object.values(row);
}
return [String(row ?? '')];
});
for (const row of normalizedRows) {
tableRows.push(makeRow(row.map((c: any) => (c === null || c === undefined ? '-' : String(c)))));
}
elements.push(
new Table({