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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user