feat(ssa): Complete QPER architecture - Query, Planner, Execute, Reflection layers

Implement the full QPER intelligent analysis pipeline:

- Phase E+: Block-based standardization for all 7 R tools, DynamicReport renderer, Word export enhancement

- Phase Q: LLM intent parsing with dynamic Zod validation against real column names, ClarificationCard component, DataProfile is_id_like tagging

- Phase P: ConfigLoader with Zod schema validation and hot-reload API, DecisionTableService (4-dimension matching), FlowTemplateService with EPV protection, PlannedTrace audit output

- Phase R: ReflectionService with statistical slot injection, sensitivity analysis conflict rules, ConclusionReport with section reveal animation, conclusion caching API, graceful R error classification

End-to-end test: 40/40 passed across two complete analysis scenarios.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-21 18:15:53 +08:00
parent 428a22adf2
commit 371e1c069c
73 changed files with 9242 additions and 706 deletions

View File

@@ -254,7 +254,59 @@ cat("AIC:", AIC(model), "\\n")
# ===== 返回结果 =====
log_add("分析完成")
# ===== 构建 report_blocks =====
blocks <- list()
# Block 1: 模型概况
blocks[[length(blocks) + 1]] <- make_kv_block(list(
"模型公式" = formula_str,
"观测数" = as.character(nrow(df)),
"预测变量数" = as.character(n_predictors),
"AIC" = as.character(round(aic, 2)),
"Nagelkerke R²" = as.character(round(r2_nagelkerke, 4)),
"EPV" = as.character(round(epv, 1))
), title = "模型概况")
# Block 2: 回归系数表
coef_headers <- c("变量", "OR", "95% CI", "P 值", "显著性")
coef_rows <- lapply(coefficients_list, function(row) {
ci_str <- sprintf("[%.3f, %.3f]", row$ci_lower, row$ci_upper)
sig <- if (row$significant) "*" else ""
c(row$variable, as.character(row$OR), ci_str, row$p_value_fmt, sig)
})
blocks[[length(blocks) + 1]] <- make_table_block(coef_headers, coef_rows, title = "回归系数表", footnote = "* P < 0.05")
# Block 3: VIF 表(如存在)
if (!is.null(vif_results) && length(vif_results) > 0) {
vif_headers <- c("变量", "VIF")
vif_rows <- lapply(vif_results, function(row) c(row$variable, as.character(row$vif)))
blocks[[length(blocks) + 1]] <- make_table_block(vif_headers, vif_rows, title = "方差膨胀因子 (VIF)")
}
# Block 4: 森林图(如存在)
if (!is.null(plot_base64)) {
blocks[[length(blocks) + 1]] <- make_image_block(plot_base64, title = "森林图", alt = "Odds Ratios Forest Plot")
}
# Block 5: 结论摘要
sig_vars <- sapply(coefficients_list, function(r) if (r$variable != "(Intercept)" && r$significant) r$variable else NULL)
sig_vars <- unlist(sig_vars[!sapply(sig_vars, is.null)])
conclusion_lines <- c(
glue("模型拟合指标AIC = {round(aic, 2)}Nagelkerke R² = {round(r2_nagelkerke, 4)}。"),
""
)
if (length(sig_vars) > 0) {
conclusion_lines <- c(conclusion_lines,
glue("α = 0.05 水平下,以下变量具有统计学意义:**{paste(sig_vars, collapse = '**, **')}**。"),
""
)
} else {
conclusion_lines <- c(conclusion_lines, "α = 0.05 水平下,无预测变量达到统计学意义。", "")
}
conclusion_lines <- c(conclusion_lines, glue("EPV = {round(epv, 1)}(建议 ≥ 10。"))
blocks[[length(blocks) + 1]] <- make_markdown_block(paste(conclusion_lines, collapse = "\n"), title = "结论摘要")
return(list(
status = "success",
message = "分析完成",
@@ -274,6 +326,7 @@ cat("AIC:", AIC(model), "\\n")
vif = vif_results,
epv = jsonlite::unbox(round(epv, 1))
),
report_blocks = blocks,
plots = if (!is.null(plot_base64)) list(plot_base64) else list(),
trace_log = logs,
reproducible_code = as.character(reproducible_code)