feat(ssa): Complete Phase 2A frontend integration - multi-step workflow end-to-end
Phase 2A: WorkflowPlannerService, WorkflowExecutorService, Python data quality, 6 bug fixes, DescriptiveResultView, multi-step R code/Word export, MVP UI reuse. V11 UI: Gemini-style, multi-task, single-page scroll, Word export. Architecture: Block-based rendering consensus (4 block types). New R tools: chi_square, correlation, descriptive, logistic_binary, mann_whitney, t_test_paired. Docs: dev summary, block-based plan, status updates, task list v2.0. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# SSA-Pro 后端开发指南
|
||||
|
||||
> **文档版本:** v1.5
|
||||
> **文档版本:** v1.7
|
||||
> **创建日期:** 2026-02-18
|
||||
> **最后更新:** 2026-02-18(纳入专家配置体系 + 决策表匹配 + R代码库)
|
||||
> **最后更新:** 2026-02-20(纳入 Prompt 体系 + 多工具流程规划 + 数据质量核查报告)
|
||||
> **目标读者:** Node.js 后端工程师
|
||||
|
||||
---
|
||||
@@ -61,6 +61,517 @@ backend/src/modules/ssa/
|
||||
|
||||
---
|
||||
|
||||
## 1.2 🆕 Prompt 体系与专家配置边界
|
||||
|
||||
> **核心理念:骨架与血肉的分离**
|
||||
>
|
||||
> 详细设计参考:`06-开发记录/SSA-Pro Prompt体系与专家配置边界梳理.md`
|
||||
|
||||
### 1.2.1 动态 Prompt 注入模式
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Prompt 动态注入架构 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ AI 工程师维护 统计专家维护 │
|
||||
│ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Prompt 模板 │ │ 配置中台 (Excel) │ │
|
||||
│ │ (骨架) │ │ - 决策表 │ │
|
||||
│ │ │ │ - 使用规则 │ │
|
||||
│ │ {{占位符}} │ ◀─────── │ - 解读模板 │ │
|
||||
│ │ │ 注入 │ - 禁用词列表 │ │
|
||||
│ └─────────────┘ └─────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ 完整 Prompt = 骨架 + 血肉 → 发送给 LLM ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2.2 三个核心 Prompt
|
||||
|
||||
| 环节 | Prompt 名称 | 作用 | 专家注入内容 |
|
||||
|------|------------|------|-------------|
|
||||
| **意图重写** | `SSA_QUERY_REWRITER` | 将医生口语翻译为统计术语 | 同义词字典 |
|
||||
| **智能规划** | `SSA_PLANNER` | 选择工具 + 生成参数映射 | 工具定义 + 使用规则 |
|
||||
| **结果解读** | `SSA_CRITIC` | 生成论文级结论 | 解读模板 + 禁用词 |
|
||||
|
||||
### 1.2.3 职责边界表
|
||||
|
||||
| 资产类型 | 谁来写? | 存在哪里? | 被谁执行? |
|
||||
|---------|---------|-----------|-----------|
|
||||
| System Prompt 模板 (骨架) | AI 工程师 | `prompt_templates` 表 | 传给 LLM |
|
||||
| 工具适用条件/数据要求 | 统计专家 | 配置中台 Excel | 注入 Prompt → LLM |
|
||||
| 统计护栏规则 | 统计专家 | 配置中台 Excel | **传给 R 服务,由 R 强执行** |
|
||||
| R 代码模板 | 统计专家 | 配置中台 Excel | 传给 R 服务 |
|
||||
| 论文结论解释规范 | 统计专家 | 配置中台 Excel | 注入 Critic Prompt → LLM |
|
||||
|
||||
> ⚠️ **关键原则**:统计护栏规则(如正态性检验 P<0.05 降级)**绝对不要**放到 Prompt 里让 LLM 判断。这些规则必须由 R 代码强逻辑执行。
|
||||
|
||||
---
|
||||
|
||||
## 1.3 🆕 多工具流程规划设计
|
||||
|
||||
> **愿景目标**:"不是执行方法,而是规划流程"
|
||||
>
|
||||
> **MVP 目标**:LLM 能够规划 2-7 个工具的串联执行流程
|
||||
|
||||
### 1.3.1 流程规划架构
|
||||
|
||||
```
|
||||
用户:"比较两组患者的血压差异"
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 多工具流程规划 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Step 1: 意图解析 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ 目的:差异比较 | 变量:连续 | 分组:二分类 | 设计:独立 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Step 2: 流程规划(LLM 输出) │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ { ││
|
||||
│ │ "workflow": [ ││
|
||||
│ │ { "step": 1, "tool": "ST_DATA_CHECK", "name": "数据校验" },││
|
||||
│ │ { "step": 2, "tool": "ST_QUALITY_REPORT", "name": "质量核查" },││
|
||||
│ │ { "step": 3, "tool": "ST_NORMALITY_TEST", "name": "正态性检验" },││
|
||||
│ │ { "step": 4, "tool": "ST_LEVENE_TEST", "name": "方差齐性" },││
|
||||
│ │ { "step": 5, "tool": "ST_T_TEST_IND", "name": "T检验" },││
|
||||
│ │ { "step": 6, "tool": "ST_EFFECT_SIZE", "name": "效应量" },││
|
||||
│ │ { "step": 7, "tool": "ST_CONCLUSION", "name": "结论生成" }││
|
||||
│ │ ], ││
|
||||
│ │ "reasoning": "两组独立样本比较,需先检验前提条件..." ││
|
||||
│ │ } ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Step 3: 串联执行 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ 工具1 → 工具2 → 工具3 → ... → 工具N ││
|
||||
│ │ ↓ ↓ ↓ ↓ ││
|
||||
│ │ 结果1 → 结果2 → 结果3 → ... → 最终报告 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.3.2 WorkflowPlannerService 设计
|
||||
|
||||
```typescript
|
||||
// planner/WorkflowPlannerService.ts
|
||||
|
||||
interface WorkflowStep {
|
||||
step: number;
|
||||
toolCode: string;
|
||||
toolName: string;
|
||||
params?: Record<string, any>;
|
||||
dependsOn?: number[]; // 依赖的前置步骤
|
||||
}
|
||||
|
||||
interface WorkflowPlan {
|
||||
workflow: WorkflowStep[];
|
||||
reasoning: string;
|
||||
estimatedTime: number; // 预估耗时(秒)
|
||||
}
|
||||
|
||||
export class WorkflowPlannerService {
|
||||
|
||||
/**
|
||||
* 生成多工具执行流程
|
||||
*/
|
||||
async generateWorkflow(
|
||||
sessionId: string,
|
||||
userQuery: string,
|
||||
dataSchema: object
|
||||
): Promise<WorkflowPlan> {
|
||||
|
||||
// 1. 获取候选工具
|
||||
const tools = await this.toolRetrieval.retrieveTools(userQuery, dataSchema, 10);
|
||||
|
||||
// 2. 构造 Prompt(包含流程规划指令)
|
||||
const prompt = this.buildWorkflowPrompt(userQuery, dataSchema, tools);
|
||||
|
||||
// 3. 调用 LLM 生成流程
|
||||
const llm = LLMFactory.getAdapter('deepseek-v3');
|
||||
const response = await llm.chat([
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'user', content: userQuery }
|
||||
]);
|
||||
|
||||
// 4. 解析 + 校验
|
||||
return this.parseWorkflowPlan(response, tools);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造流程规划 Prompt
|
||||
*/
|
||||
private buildWorkflowPrompt(query: string, schema: object, tools: any[]): string {
|
||||
return `
|
||||
你是一位顶尖的临床数据科学家。你拥有一个包含 ${tools.length} 个专家级统计工具的代码库。
|
||||
|
||||
用户数据结构:
|
||||
${JSON.stringify(schema, null, 2)}
|
||||
|
||||
可用工具库:
|
||||
${tools.map(t => `- ${t.toolCode}: ${t.name} - ${t.description}`).join('\n')}
|
||||
|
||||
请根据用户需求,规划一个完整的统计分析流程。流程应包含:
|
||||
1. 数据质量核查(必须)
|
||||
2. 前提条件检验(如适用)
|
||||
3. 核心统计分析
|
||||
4. 效应量计算(如适用)
|
||||
5. 结论生成
|
||||
|
||||
输出 JSON 格式:
|
||||
{
|
||||
"workflow": [
|
||||
{ "step": 1, "toolCode": "工具代码", "toolName": "工具名称" },
|
||||
...
|
||||
],
|
||||
"reasoning": "规划理由"
|
||||
}
|
||||
|
||||
只输出 JSON,不要其他内容。
|
||||
`.trim();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3.3 WorkflowExecutorService 设计
|
||||
|
||||
```typescript
|
||||
// executor/WorkflowExecutorService.ts
|
||||
|
||||
interface StepResult {
|
||||
step: number;
|
||||
toolCode: string;
|
||||
status: 'success' | 'warning' | 'error';
|
||||
result?: any;
|
||||
error?: string;
|
||||
executionMs: number;
|
||||
}
|
||||
|
||||
export class WorkflowExecutorService {
|
||||
|
||||
/**
|
||||
* 串联执行多个工具
|
||||
*/
|
||||
async executeWorkflow(
|
||||
sessionId: string,
|
||||
workflow: WorkflowStep[],
|
||||
onStepComplete?: (stepResult: StepResult) => void // 实时回调
|
||||
): Promise<StepResult[]> {
|
||||
|
||||
const results: StepResult[] = [];
|
||||
let previousResult: any = null;
|
||||
|
||||
for (const step of workflow) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构造本步骤的输入(可能依赖前置步骤的输出)
|
||||
const input = this.buildStepInput(step, previousResult, sessionId);
|
||||
|
||||
// 调用 R 服务执行
|
||||
const result = await this.rClient.execute(sessionId, {
|
||||
tool_code: step.toolCode,
|
||||
params: input
|
||||
});
|
||||
|
||||
const stepResult: StepResult = {
|
||||
step: step.step,
|
||||
toolCode: step.toolCode,
|
||||
status: result.status === 'success' ? 'success' : 'warning',
|
||||
result: result,
|
||||
executionMs: Date.now() - startTime
|
||||
};
|
||||
|
||||
results.push(stepResult);
|
||||
previousResult = result;
|
||||
|
||||
// 实时通知前端
|
||||
onStepComplete?.(stepResult);
|
||||
|
||||
} catch (error: any) {
|
||||
const stepResult: StepResult = {
|
||||
step: step.step,
|
||||
toolCode: step.toolCode,
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
executionMs: Date.now() - startTime
|
||||
};
|
||||
|
||||
results.push(stepResult);
|
||||
onStepComplete?.(stepResult);
|
||||
|
||||
// 决定是否继续执行后续步骤
|
||||
if (this.isCriticalError(error)) {
|
||||
break; // 关键错误,中断流程
|
||||
}
|
||||
// 非关键错误,继续执行
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.4 🆕 数据质量核查报告设计
|
||||
|
||||
> **愿景目标**:自动生成"数据体检报告",主动告诉用户数据有什么问题
|
||||
>
|
||||
> **MVP 目标**:在执行分析前,先生成数据质量核查报告
|
||||
|
||||
### 1.4.1 核查报告结构
|
||||
|
||||
```typescript
|
||||
// types/DataQualityReport.ts
|
||||
|
||||
interface DataQualityReport {
|
||||
// 基础统计
|
||||
summary: {
|
||||
totalRows: number;
|
||||
totalColumns: number;
|
||||
numericColumns: number;
|
||||
categoricalColumns: number;
|
||||
};
|
||||
|
||||
// 缺失值分析
|
||||
missingAnalysis: {
|
||||
totalMissing: number;
|
||||
missingRate: number; // 总体缺失率
|
||||
columns: Array<{
|
||||
name: string;
|
||||
missingCount: number;
|
||||
missingRate: number;
|
||||
suggestion: string; // 处理建议
|
||||
}>;
|
||||
};
|
||||
|
||||
// 异常值检测
|
||||
outlierAnalysis: {
|
||||
columns: Array<{
|
||||
name: string;
|
||||
outlierCount: number;
|
||||
outlierValues: any[];
|
||||
method: 'IQR' | 'ZScore';
|
||||
suggestion: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
// 分布检验(数值变量)
|
||||
distributionAnalysis: {
|
||||
columns: Array<{
|
||||
name: string;
|
||||
shapiroP: number;
|
||||
isNormal: boolean;
|
||||
skewness: number;
|
||||
kurtosis: number;
|
||||
suggestion: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
// 分组平衡性(如有分组变量)
|
||||
groupBalance?: {
|
||||
groupColumn: string;
|
||||
groups: Array<{
|
||||
name: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}>;
|
||||
isBalanced: boolean;
|
||||
suggestion: string;
|
||||
};
|
||||
|
||||
// 整体评估
|
||||
overallAssessment: {
|
||||
qualityScore: number; // 0-100
|
||||
level: 'good' | 'acceptable' | 'poor';
|
||||
warnings: string[];
|
||||
recommendations: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4.2 DataQualityService 设计
|
||||
|
||||
```typescript
|
||||
// planner/DataQualityService.ts
|
||||
|
||||
export class DataQualityService {
|
||||
|
||||
/**
|
||||
* 生成数据质量核查报告
|
||||
* 这是 R 服务调用,不是 LLM
|
||||
*/
|
||||
async generateReport(sessionId: string): Promise<DataQualityReport> {
|
||||
|
||||
// 调用 R 服务的数据质量核查工具
|
||||
const result = await this.rClient.execute(sessionId, {
|
||||
tool_code: 'ST_QUALITY_REPORT',
|
||||
params: {
|
||||
check_missing: true,
|
||||
check_outliers: true,
|
||||
check_distribution: true,
|
||||
check_balance: true
|
||||
}
|
||||
});
|
||||
|
||||
return this.transformToReport(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户友好的摘要(可选用 LLM 增强)
|
||||
*/
|
||||
async generateSummary(report: DataQualityReport): Promise<string> {
|
||||
const llm = LLMFactory.getAdapter('deepseek-v3');
|
||||
|
||||
const prompt = `
|
||||
你是一位数据分析专家。请根据以下数据质量核查结果,生成一段简洁的中文摘要(3-5句话),
|
||||
告诉用户数据的整体质量如何,主要问题是什么,是否可以继续分析。
|
||||
|
||||
核查结果:
|
||||
${JSON.stringify(report, null, 2)}
|
||||
|
||||
请直接输出摘要文本。
|
||||
`;
|
||||
|
||||
return await llm.chat([{ role: 'user', content: prompt }]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4.3 R 服务端实现(ST_QUALITY_REPORT)
|
||||
|
||||
```r
|
||||
# tools/quality_report.R
|
||||
|
||||
#' @tool_code ST_QUALITY_REPORT
|
||||
#' @name 数据质量核查报告
|
||||
#' @description 生成全面的数据质量评估报告
|
||||
|
||||
run_analysis <- function(input) {
|
||||
# 加载数据
|
||||
df <- load_data(input)
|
||||
|
||||
report <- list()
|
||||
|
||||
# 1. 基础统计
|
||||
report$summary <- list(
|
||||
totalRows = nrow(df),
|
||||
totalColumns = ncol(df),
|
||||
numericColumns = sum(sapply(df, is.numeric)),
|
||||
categoricalColumns = sum(sapply(df, is.character) | sapply(df, is.factor))
|
||||
)
|
||||
|
||||
# 2. 缺失值分析
|
||||
report$missingAnalysis <- analyze_missing(df)
|
||||
|
||||
# 3. 异常值检测(IQR 方法)
|
||||
report$outlierAnalysis <- analyze_outliers(df)
|
||||
|
||||
# 4. 分布检验
|
||||
report$distributionAnalysis <- analyze_distribution(df)
|
||||
|
||||
# 5. 分组平衡性
|
||||
if (!is.null(input$group_var)) {
|
||||
report$groupBalance <- analyze_balance(df, input$group_var)
|
||||
}
|
||||
|
||||
# 6. 整体评估
|
||||
report$overallAssessment <- calculate_quality_score(report)
|
||||
|
||||
return(list(
|
||||
status = "success",
|
||||
report = report
|
||||
))
|
||||
}
|
||||
|
||||
# 计算整体质量评分
|
||||
calculate_quality_score <- function(report) {
|
||||
score <- 100
|
||||
warnings <- c()
|
||||
recommendations <- c()
|
||||
|
||||
# 缺失值扣分
|
||||
if (report$missingAnalysis$missingRate > 0.1) {
|
||||
score <- score - 20
|
||||
warnings <- c(warnings, "缺失值比例超过10%")
|
||||
recommendations <- c(recommendations, "建议处理缺失值后再进行分析")
|
||||
} else if (report$missingAnalysis$missingRate > 0.05) {
|
||||
score <- score - 10
|
||||
warnings <- c(warnings, "存在一定比例的缺失值")
|
||||
}
|
||||
|
||||
# 异常值扣分
|
||||
outlier_cols <- sum(sapply(report$outlierAnalysis$columns, function(x) x$outlierCount > 0))
|
||||
if (outlier_cols > 0) {
|
||||
score <- score - 5 * outlier_cols
|
||||
warnings <- c(warnings, paste0(outlier_cols, "个变量存在异常值"))
|
||||
}
|
||||
|
||||
# 非正态扣分(提示,不强制扣分)
|
||||
non_normal <- sum(!sapply(report$distributionAnalysis$columns, function(x) x$isNormal))
|
||||
if (non_normal > 0) {
|
||||
recommendations <- c(recommendations,
|
||||
paste0(non_normal, "个变量不满足正态分布,系统将自动选择非参数方法"))
|
||||
}
|
||||
|
||||
# 确定等级
|
||||
level <- if (score >= 80) "good" else if (score >= 60) "acceptable" else "poor"
|
||||
|
||||
return(list(
|
||||
qualityScore = max(0, score),
|
||||
level = level,
|
||||
warnings = warnings,
|
||||
recommendations = recommendations
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4.4 前端展示(核查报告卡片)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 📊 数据质量核查报告 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 📈 数据概况 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ 总样本量:200 行 × 15 列 ││
|
||||
│ │ 数值变量:8 个 | 分类变量:7 个 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
│ ⚠️ 发现的问题 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ • 缺失值:血压字段有 12 例缺失 (6%) ││
|
||||
│ │ • 异常值:2 例血压 > 300 mmHg(疑似记录错误) ││
|
||||
│ │ • 正态性:治疗组血压不满足正态分布 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
│ 💡 系统建议 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ 1. 建议处理 2 例异常值后再分析 ││
|
||||
│ │ 2. 由于正态性不满足,系统将自动选择非参数方法 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
│ 🎯 整体评估:良好 (82/100) │
|
||||
│ │
|
||||
│ [继续分析] [下载报告] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库 Schema(Prisma)
|
||||
|
||||
```prisma
|
||||
@@ -683,6 +1194,45 @@ export class ToolRetrievalService {
|
||||
|
||||
### 4.3 PlannerService(AI 规划 + JSON 容错)
|
||||
|
||||
#### 🆕 Prompt 世界观设计(智能化演进关键)
|
||||
|
||||
> **重要**:Prompt 的"世界观"设计直接影响 LLM 的推理质量和未来演进能力。
|
||||
>
|
||||
> 详细背景参考:`04-开发计划/06-智能化演进共识与MVP执行计划.md`
|
||||
|
||||
**错误的世界观(接线员思维):**
|
||||
```
|
||||
你是一个工具选择器,从以下列表中选择合适的工具...
|
||||
```
|
||||
|
||||
**正确的世界观(数据科学家思维):**
|
||||
```
|
||||
你是一位顶尖的临床数据科学家,拥有以下能力:
|
||||
|
||||
1. 深刻理解医学研究的统计学需求
|
||||
2. 精通各类统计方法的适用场景和前提条件
|
||||
3. 能够诊断数据特征并选择最优分析路径
|
||||
|
||||
你现在拥有一个包含 100+ 专家级统计算法的代码库。
|
||||
每个算法都经过统计学专家的严格验证,确保结果的权威性。
|
||||
|
||||
请理解医生的研究意图,诊断数据特征,从代码库中选择最合适的工具组合,
|
||||
并制定完整的分析计划。你的目标是帮助医生产出可以直接用于 SCI 论文的统计结果。
|
||||
```
|
||||
|
||||
**为什么这很重要?**
|
||||
|
||||
| 维度 | 接线员思维 | 数据科学家思维 |
|
||||
|------|-----------|---------------|
|
||||
| LLM 自我认知 | 被动的工具选择器 | 主动的分析规划师 |
|
||||
| 推理深度 | 简单匹配 | 深度分析数据特征 |
|
||||
| 输出质量 | 可能选错工具 | 更准确的工具选择 |
|
||||
| Phase 3 演进 | 难以扩展到代码修改 | 自然过渡到代码理解和修改 |
|
||||
|
||||
> **MVP 阶段行动**:更新 `SSA_PLANNER` Prompt 模板,使用"数据科学家"世界观。
|
||||
|
||||
---
|
||||
|
||||
```typescript
|
||||
// services/PlannerService.ts
|
||||
import { LLMFactory } from '@/common/llm/adapters/LLMFactory';
|
||||
|
||||
Reference in New Issue
Block a user