Przeglądaj źródła

refactor(s8): standardize severity to follow and serious

S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:S8 异常 severity 业务枚举从
LOW/MEDIUM/HIGH/CRITICAL 统一迁移为 FOLLOW(关注)/ SERIOUS(严重)。

新增 S8SeverityCode helper:常量 + Normalize(LOW/MEDIUM→FOLLOW;HIGH/CRITICAL→SERIOUS)+ IsFollow/IsSerious + Label + Options,旧值作为 legacy 兼容输入永远不再写入 DB。

后端:
- 5 张实体默认值 MEDIUM → FOLLOW(AdoS8Exception/Type/AlertRule/WatchRule/NotificationLayer)
- DTO 默认值 + XMLDoc 同步更新(AdoS8Dtos)
- S8Labels 切换为 2 档输出
- S8DictionaryService S8_EXCEPTION_SEVERITY 字典只返回关注/严重
- S8MonitoringService red/yellow/green 改用 IsFollow/IsSerious;green 不再来自 severity
- S8DashboardService bySeverity 归一两桶;critical 计数语义切为 SERIOUS;byProcess 增加 followCount/seriousCount + S1-S7 排序
- 3 处建单点 + 3 处规则评估器写入前 Normalize;S8ManualReport 白名单接受新值并保留 legacy 兼容
- S8WatchScheduler 兜底 MEDIUM 改 Normalize 兜底 FOLLOW
- S8NotificationLayer 增删改查全部 Normalize;Resolver 匹配 Normalize
- S8ExceptionService q.Severity Normalize 兼容 legacy 查询参数
- SeedData 44 条 typeCode baseline 字面量同步替换

前端:
- Dashboard 流程环节柱:从单色单段升级为关注/严重黄红堆叠柱(S1→S7 顺序)
- Dashboard severity 下拉 4 档 → 2 档
- AnomalyDrillPanel SEVERITY_OPTS 改 2 档;severityTagType 增加 legacy 兜底
- 配置页(AlertRule/ExceptionType/Notification/WatchRule/ManualReport)默认 MEDIUM → FOLLOW;硬编码 4 档下拉改 2 档
- 监控大屏 AnomalyModuleTable / AnomalySummaryBar / OrderGridCell 文案"紧急/高"改"严重";OrderGridCell 同步将"预警"改"关注"
- S8DashboardApi 类型新增 followCount/seriousCount

迁移:
- 6 份 demo SQL 字面量同步替换(doc/lwb/)
- 1 份 DB 迁移 SQL(doc/lwb/S8/20260502/severity-follow-serious-migration.sql)
YY968XX 1 miesiąc temu
rodzic
commit
8d6aa4b54a
33 zmienionych plików z 286 dodań i 150 usunięć
  1. 3 0
      Web/src/views/aidop/s8/api/s8DashboardApi.ts
  2. 1 1
      Web/src/views/aidop/s8/config/S8AlertRulesPage.vue
  3. 1 1
      Web/src/views/aidop/s8/config/S8ExceptionTypeConfigPage.vue
  4. 5 6
      Web/src/views/aidop/s8/config/S8NotificationLayerPage.vue
  5. 14 9
      Web/src/views/aidop/s8/config/S8WatchRuleConfigPage.vue
  6. 64 11
      Web/src/views/aidop/s8/dashboard/S8DashboardPage.vue
  7. 7 7
      Web/src/views/aidop/s8/monitoring/components/AnomalyDrillPanel.vue
  8. 1 1
      Web/src/views/aidop/s8/monitoring/components/AnomalyModuleTable.vue
  9. 1 1
      Web/src/views/aidop/s8/monitoring/components/AnomalySummaryBar.vue
  10. 6 4
      Web/src/views/aidop/s8/monitoring/components/MonitorColorRuleDrawer.vue
  11. 2 2
      Web/src/views/aidop/s8/monitoring/components/OrderGridCell.vue
  12. 1 1
      Web/src/views/aidop/s8/report/S8ManualReportPage.vue
  13. 4 4
      server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs
  14. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8AlertRule.cs
  15. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Exception.cs
  16. 2 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8ExceptionType.cs
  17. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8NotificationLayer.cs
  18. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8WatchRule.cs
  19. 4 11
      server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8Labels.cs
  20. 53 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8SeverityCode.cs
  21. 44 44
      server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/S8ExceptionTypeSeedData.cs
  22. 1 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/IS8RuleEvaluator.cs
  23. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8OutOfRangeRuleEvaluator.cs
  24. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8ShortageRuleEvaluator.cs
  25. 2 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8TimeoutRuleEvaluator.cs
  26. 11 3
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DashboardService.cs
  27. 3 4
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DictionaryService.cs
  28. 4 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs
  29. 15 16
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ManualReportService.cs
  30. 13 9
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs
  31. 3 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8NotificationLayerResolver.cs
  32. 5 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8NotificationLayerService.cs
  33. 4 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8WatchSchedulerService.cs

+ 3 - 0
Web/src/views/aidop/s8/api/s8DashboardApi.ts

@@ -27,6 +27,9 @@ export interface S8DeptDistItem extends S8DistItem {
 
 export interface S8ProcessDistItem extends S8DistItem {
 	nodeName: string;
+	// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:黄红堆叠柱用。
+	followCount?: number;
+	seriousCount?: number;
 }
 
 export interface S8Distributions {

+ 1 - 1
Web/src/views/aidop/s8/config/S8AlertRulesPage.vue

@@ -25,7 +25,7 @@ const fields = [
 const buildDefault = () => ({
 	ruleCode: '',
 	sceneCode: '',
-	severity: 'MEDIUM',
+	severity: 'FOLLOW',
 	triggerCondition: '',
 	thresholdVal: '',
 	timeWindow: '',

+ 1 - 1
Web/src/views/aidop/s8/config/S8ExceptionTypeConfigPage.vue

@@ -58,7 +58,7 @@ const buildDefault = () => ({
 	typeName: '',
 	sceneCode: '',
 	monitoringCategoryKey: '',
-	severityDefault: 'MEDIUM',
+	severityDefault: 'FOLLOW',
 	slaMinutes: 60,
 	ownerRoleCode: '',
 	escalateRoleCode: '',

+ 5 - 6
Web/src/views/aidop/s8/config/S8NotificationLayerPage.vue

@@ -91,11 +91,10 @@ import service from '/@/utils/request';
 
 // ── static option sets ──────────────────────────────────────────────────────
 
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举两档。
 const severityOptions = [
-	{ value: 'LOW', label: '低' },
-	{ value: 'MEDIUM', label: '中' },
-	{ value: 'HIGH', label: '高' },
-	{ value: 'CRITICAL', label: '严重' },
+	{ value: 'FOLLOW',  label: '关注' },
+	{ value: 'SERIOUS', label: '严重' },
 ];
 
 const levelOptions = [
@@ -129,7 +128,7 @@ const formRef = ref<FormInstance>();
 
 const defaultForm = () => ({
 	sceneCode: '',
-	severity: 'MEDIUM',
+	severity: 'FOLLOW',
 	levelCode: 'L1_OPERATOR',
 	targetRoleIdList: [] as string[],
 	notifyChannelList: [] as string[],
@@ -217,7 +216,7 @@ function openEdit(row: any) {
 	editId.value = row.id;
 	Object.assign(form, {
 		sceneCode: row.sceneCode ?? '',
-		severity: row.severity ?? 'MEDIUM',
+		severity: row.severity ?? 'FOLLOW',
 		levelCode: row.levelCode ?? 'L1_OPERATOR',
 		targetRoleIdList: row.targetRoleIds ? row.targetRoleIds.split(',').filter(Boolean) : [],
 		notifyChannelList: row.notifyChannel ? row.notifyChannel.split(',').filter(Boolean) : [],

+ 14 - 9
Web/src/views/aidop/s8/config/S8WatchRuleConfigPage.vue

@@ -151,20 +151,26 @@ const sceneCodeOptions = computed(() =>
 	[...new Set(rows.value.map(r => r.sceneCode).filter((c): c is string => !!c))].sort(),
 );
 
-// A-03: severity helpers
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举两档;旧值兜底兼容回显。
 function severityLabel(s?: string | null): string {
 	switch ((s ?? '').toUpperCase()) {
-		case 'HIGH': return '高';
-		case 'MEDIUM': return '中';
-		case 'LOW': return '低';
+		case 'SERIOUS':
+		case 'CRITICAL':
+		case 'HIGH': return '严重';
+		case 'FOLLOW':
+		case 'MEDIUM':
+		case 'LOW': return '关注';
 		default: return s || '—';
 	}
 }
 function severityTagType(s?: string | null): 'danger' | 'warning' | 'info' | '' {
 	switch ((s ?? '').toUpperCase()) {
+		case 'SERIOUS':
+		case 'CRITICAL':
 		case 'HIGH': return 'danger';
-		case 'MEDIUM': return 'warning';
-		case 'LOW': return 'info';
+		case 'FOLLOW':
+		case 'MEDIUM':
+		case 'LOW': return 'warning';
 		default: return '';
 	}
 }
@@ -833,9 +839,8 @@ onDeactivated(() => stopAutoRefresh());
 				<el-option v-for="c in sceneCodeOptions" :key="c" :label="c" :value="c" />
 			</el-select>
 			<el-select v-model="filterSeverity" size="small" clearable placeholder="严重度筛选" style="width: 120px">
-				<el-option label="高" value="HIGH" />
-				<el-option label="中" value="MEDIUM" />
-				<el-option label="低" value="LOW" />
+				<el-option label="关注" value="FOLLOW" />
+				<el-option label="严重" value="SERIOUS" />
 			</el-select>
 			<span class="rule-toolbar__hint">每 30 秒自动刷新;编辑抽屉打开期间暂停刷新</span>
 		</div>

+ 64 - 11
Web/src/views/aidop/s8/dashboard/S8DashboardPage.vue

@@ -178,8 +178,13 @@ const trendChartRef = ref<HTMLElement | null>(null);
 let trendChart: echarts.ECharts | null = null;
 
 // ─── 工具函数 ─────────────────────────────────────────────────
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举 FOLLOW(关注)/ SERIOUS(严重);
+// 旧值 LOW/MEDIUM/HIGH/CRITICAL 通过 fallback 映射到 warning/danger,便于 legacy 数据回显。
 function severityTagType(s: string) {
-	return ({ CRITICAL: 'danger', HIGH: 'warning', MEDIUM: '', LOW: 'success' } as any)[s] ?? 'info';
+	const upper = String(s ?? '').toUpperCase();
+	if (upper === 'SERIOUS' || upper === 'HIGH' || upper === 'CRITICAL') return 'danger';
+	if (upper === 'FOLLOW' || upper === 'LOW' || upper === 'MEDIUM') return 'warning';
+	return 'info';
 }
 function statusTagType(s: string) {
 	return ({ NEW: 'info', ASSIGNED: '', IN_PROGRESS: 'warning', RESOLVED: 'success', CLOSED: 'success' } as any)[s] ?? 'info';
@@ -214,18 +219,68 @@ async function renderMainChart() {
 	let barColor = '#6366f1';
 	let title = '';
 
+	// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:流程环节面板专用 — 关注/严重 黄红堆叠柱。
+	if (panel === 'process') {
+		const src = (distributions.value?.byProcess ?? []) as Array<{ key: string; nodeName?: string; count: number; followCount?: number; seriousCount?: number }>;
+		// 按 S1-S7 顺序固定排序(从下往上:bar 反向,axis 反向,使 S1 显示在最上方)。
+		const ORDER = ['S1','S2','S3','S4','S5','S6','S7'];
+		const ordered = [...src].sort((a, b) => ORDER.indexOf(a.key) - ORDER.indexOf(b.key)).reverse();
+		const cats = ordered.map(x => x.nodeName || x.key);
+		const followVals = ordered.map(x => x.followCount ?? 0);
+		const seriousVals = ordered.map(x => x.seriousCount ?? 0);
+		const totalAll = followVals.reduce((s, v) => s + v, 0) + seriousVals.reduce((s, v) => s + v, 0);
+		mainChartEmpty.value = totalAll === 0;
+		if (mainChartEmpty.value) return;
+
+		mainChart.setOption({
+			title: { text: '流程节点异常分布', left: 4, top: 6, textStyle: { fontSize: 14, fontWeight: 700, color: '#1e293b' } },
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: { type: 'shadow' },
+				formatter: (ps: any) => {
+					if (!Array.isArray(ps) || ps.length === 0) return '';
+					const cat = ps[0].axisValueLabel ?? ps[0].name;
+					const followV = ps.find((p: any) => p.seriesName === '关注')?.value ?? 0;
+					const seriousV = ps.find((p: any) => p.seriesName === '严重')?.value ?? 0;
+					return `${cat}<br/>关注:${followV}<br/>严重:${seriousV}<br/>合计:${followV + seriousV}`;
+				},
+			},
+			legend: { data: ['关注', '严重'], top: 8, right: 12 },
+			grid: { left: 140, right: 48, top: 44, bottom: 20 },
+			xAxis: { type: 'value', splitLine: { lineStyle: { color: '#f1f5f9' } } },
+			yAxis: {
+				type: 'category',
+				data: cats,
+				axisLabel: { width: 128, overflow: 'truncate', color: '#475569', fontSize: 12 },
+				axisLine: { lineStyle: { color: '#e2e8f0' } },
+			},
+			series: [
+				{ name: '关注',  type: 'bar', stack: 'sev', data: followVals,  itemStyle: { color: '#f59e0b' }, barMaxWidth: 24 },
+				{ name: '严重',  type: 'bar', stack: 'sev', data: seriousVals, itemStyle: { color: '#ef4444', borderRadius: [0, 4, 4, 0] }, barMaxWidth: 24,
+				  label: { show: true, position: 'right', color: '#64748b', fontSize: 12, formatter: (p: any) => {
+				    const idx = p.dataIndex; return String((followVals[idx] ?? 0) + (seriousVals[idx] ?? 0));
+				  } },
+				},
+			],
+		}, true);
+
+		// drill 用 categories(流程环节中文名 → byProcess.key 反查 = moduleCode)
+		categories = cats;
+		mainChart.off('click');
+		mainChart.on('click', (params: any) => {
+			chartDrillValue.value = categories[params.dataIndex] ?? '';
+			detailPage.value = 1;
+			void loadDetail();
+		});
+		return;
+	}
+
 	if (panel === 'object') {
 		const src = distributions.value?.byObject ?? [];
 		categories = src.map(x => x.key);
 		values     = src.map(x => x.count);
 		title = '受影响订单';
 		barColor = '#6366f1';
-	} else if (panel === 'process') {
-		const src = distributions.value?.byProcess ?? [];
-		categories = src.map(x => x.nodeName || x.key);
-		values     = src.map(x => x.count);
-		title = '流程节点异常分布';
-		barColor = '#3b82f6';
 	} else if (panel === 'occurrence') {
 		const src = distributions.value?.byOccurrenceDept ?? [];
 		categories = src.map(x => x.deptName || String(x.key));
@@ -533,10 +588,8 @@ onBeforeUnmount(() => { mainChart?.dispose(); trendChart?.dispose(); });
 				<el-form inline class="s8-filter-form">
 					<el-form-item label="严重度">
 						<el-select v-model="currentPanelFilter.severity" clearable placeholder="全部" style="width:120px">
-							<el-option label="严重" value="CRITICAL" />
-							<el-option label="高"   value="HIGH" />
-							<el-option label="中"   value="MEDIUM" />
-							<el-option label="低"   value="LOW" />
+							<el-option label="关注" value="FOLLOW" />
+							<el-option label="严重" value="SERIOUS" />
 						</el-select>
 					</el-form-item>
 					<el-form-item label="开始日期">

+ 7 - 7
Web/src/views/aidop/s8/monitoring/components/AnomalyDrillPanel.vue

@@ -139,11 +139,10 @@ const STATUS_OPTS = [
 	{ value: 'RESOLVED',    label: '已处理' },
 	{ value: 'CLOSED',      label: '已关闭' },
 ];
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举两档。
 const SEVERITY_OPTS = [
-	{ value: 'CRITICAL', label: '紧急' },
-	{ value: 'HIGH',     label: '高' },
-	{ value: 'MEDIUM',   label: '中' },
-	{ value: 'LOW',      label: '低' },
+	{ value: 'FOLLOW',  label: '关注' },
+	{ value: 'SERIOUS', label: '严重' },
 ];
 
 const query = reactive({
@@ -223,10 +222,11 @@ function onRowClick(row: S8ExceptionRow) {
 	router.push(`/aidop/s8/exceptions/${row.id}`);
 }
 
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:严重=红 / 关注=黄;旧值兜底兼容。
 function severityTagType(severity: string): 'danger' | 'warning' | 'primary' | 'info' {
-	if (severity === 'CRITICAL') return 'danger';
-	if (severity === 'HIGH') return 'warning';
-	if (severity === 'MEDIUM') return 'primary';
+	const upper = String(severity ?? '').toUpperCase();
+	if (upper === 'SERIOUS' || upper === 'CRITICAL' || upper === 'HIGH') return 'danger';
+	if (upper === 'FOLLOW' || upper === 'MEDIUM' || upper === 'LOW') return 'warning';
 	return 'info';
 }
 

+ 1 - 1
Web/src/views/aidop/s8/monitoring/components/AnomalyModuleTable.vue

@@ -25,7 +25,7 @@
 					</td>
 				</tr>
 				<tr>
-					<th scope="row">紧急/高</th>
+					<th scope="row">严重</th>
 					<td
 						v-for="m in modules"
 						:key="'r-' + m.moduleCode"

+ 1 - 1
Web/src/views/aidop/s8/monitoring/components/AnomalySummaryBar.vue

@@ -16,7 +16,7 @@
 			<div class="asb__badge asb__badge--red">
 				<span class="asb__dot asb__dot--red" />
 				<span class="asb__num">{{ summary.red }}</span>
-				<span class="asb__label">紧急/高</span>
+				<span class="asb__label">严重</span>
 			</div>
 			<div class="asb__badge asb__badge--yellow">
 				<span class="asb__dot asb__dot--yellow" />

+ 6 - 4
Web/src/views/aidop/s8/monitoring/components/MonitorColorRuleDrawer.vue

@@ -70,20 +70,22 @@ export interface ColorRule {
 
 const STORAGE_KEY = 's8_monitor_color_rule';
 
+// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:选项语义改为关注/严重;旧 KEY 字面量保留不变
+// (仅作 storage key,前端解释一致),label 同步更新便于阅读。
 const DEFAULT_RULE: ColorRule = {
 	red: 'CRITICAL_HIGH_OR_TIMEOUT',
 	yellow: 'MEDIUM',
 };
 
 const redOptions = [
-	{ value: 'CRITICAL_HIGH_OR_TIMEOUT', label: '有CRITICAL/HIGH未关闭,或黄色超时' },
-	{ value: 'CRITICAL_HIGH', label: '有CRITICAL/HIGH未关闭' },
+	{ value: 'CRITICAL_HIGH_OR_TIMEOUT', label: '有严重未关闭,或关注超时' },
+	{ value: 'CRITICAL_HIGH', label: '有严重未关闭' },
 	{ value: 'ANY_UNCLOSED', label: '有任意未关闭异常' },
 ];
 
 const yellowOptions = [
-	{ value: 'MEDIUM', label: '有MEDIUM未关闭' },
-	{ value: 'MEDIUM_OR_LOW', label: '有MEDIUM或LOW未关闭' },
+	{ value: 'MEDIUM', label: '有关注未关闭' },
+	{ value: 'MEDIUM_OR_LOW', label: '有关注未关闭(含低优先)' },
 ];
 
 const visible = defineModel<boolean>({ required: true });

+ 2 - 2
Web/src/views/aidop/s8/monitoring/components/OrderGridCell.vue

@@ -18,8 +18,8 @@
 				<span class="ogc__kpi-unit">个异常</span>
 			</div>
 			<div class="ogc__kpi-secondary">
-				<span v-if="module.red > 0" class="ogc__kpi-tag ogc__kpi-tag--red">紧急 {{ module.red }}</span>
-				<span v-if="module.yellow > 0" class="ogc__kpi-tag ogc__kpi-tag--yellow">预警 {{ module.yellow }}</span>
+				<span v-if="module.red > 0" class="ogc__kpi-tag ogc__kpi-tag--red">严重 {{ module.red }}</span>
+				<span v-if="module.yellow > 0" class="ogc__kpi-tag ogc__kpi-tag--yellow">关注 {{ module.yellow }}</span>
 				<span class="ogc__kpi-tag ogc__kpi-tag--green">正常 {{ module.green }}</span>
 			</div>
 		</div>

+ 1 - 1
Web/src/views/aidop/s8/report/S8ManualReportPage.vue

@@ -27,7 +27,7 @@ const formOptions = reactive<{
 const form = reactive({
 	title: '',
 	sceneCode: '',
-	severity: 'MEDIUM',
+	severity: 'FOLLOW',
 	description: '',
 	occurrenceDeptId: undefined as number | undefined,
 	responsibleDeptId: undefined as number | undefined,

+ 4 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -92,7 +92,7 @@ public class AdoS8ManualReportCreateDto
     public string Title { get; set; } = string.Empty;
     public string? Description { get; set; }
     public string SceneCode { get; set; } = string.Empty;
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
     public long OccurrenceDeptId { get; set; }
     public long ResponsibleDeptId { get; set; }
     public long? ReporterId { get; set; }
@@ -207,11 +207,11 @@ public class AdoS8ModuleOrderSummary
 {
     public string ModuleCode { get; set; } = string.Empty;
     public string ModuleLabel { get; set; } = string.Empty;
-    /// <summary>绿色订单数(该阶段无未关闭异常)</summary>
+    /// <summary>绿色订单数(来自订单主数据,不从异常 severity 推导)。</summary>
     public int Green { get; set; }
-    /// <summary>黄色订单数(有 MEDIUM 未关闭)</summary>
+    /// <summary>黄色订单数(FOLLOW 关注)。</summary>
     public int Yellow { get; set; }
-    /// <summary>红色订单数(有 CRITICAL/HIGH 未关闭,或黄色超时)</summary>
+    /// <summary>红色订单数(SERIOUS 严重,或黄色超时)。</summary>
     public int Red { get; set; }
     public int Total { get; set; }
     /// <summary>异常发生频率(每百订单异常数)</summary>

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8AlertRule.cs

@@ -18,8 +18,9 @@ public class AdoS8AlertRule
     [SugarColumn(ColumnName = "scene_code", Length = 64)]
     public string SceneCode { get; set; } = string.Empty;
 
+    /// <summary>S8 业务枚举 FOLLOW(关注)/ SERIOUS(严重)。</summary>
     [SugarColumn(ColumnName = "severity", Length = 32)]
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
 
     [SugarColumn(ColumnName = "trigger_condition", Length = 512, IsNullable = true)]
     public string? TriggerCondition { get; set; }

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8Exception.cs

@@ -39,8 +39,9 @@ public class AdoS8Exception
     [SugarColumn(ColumnName = "status", Length = 32)]
     public string Status { get; set; } = "NEW";
 
+    /// <summary>S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举 FOLLOW(关注)/ SERIOUS(严重)。</summary>
     [SugarColumn(ColumnName = "severity", Length = 32)]
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
 
     [SugarColumn(ColumnName = "priority_score", ColumnDataType = "decimal(10,2)")]
     public decimal PriorityScore { get; set; }

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8ExceptionType.cs

@@ -28,9 +28,9 @@ public class AdoS8ExceptionType
     [SugarColumn(ColumnName = "scene_code", Length = 64)]
     public string SceneCode { get; set; } = string.Empty;
 
-    /// <summary>默认严重度:LOW / MEDIUM / HIGH / CRITICAL</summary>
+    /// <summary>默认严重度:FOLLOW(关注)/ SERIOUS(严重)。S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1。</summary>
     [SugarColumn(ColumnName = "severity_default", Length = 16)]
-    public string SeverityDefault { get; set; } = "MEDIUM";
+    public string SeverityDefault { get; set; } = "FOLLOW";
 
     /// <summary>默认 SLA(分钟),超时触发报警</summary>
     [SugarColumn(ColumnName = "sla_minutes")]

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8NotificationLayer.cs

@@ -15,8 +15,9 @@ public class AdoS8NotificationLayer
     [SugarColumn(ColumnName = "scene_code", Length = 64)]
     public string SceneCode { get; set; } = string.Empty;
 
+    /// <summary>S8 业务枚举 FOLLOW(关注)/ SERIOUS(严重)。</summary>
     [SugarColumn(ColumnName = "severity", Length = 32)]
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
 
     [SugarColumn(ColumnName = "level_code", Length = 32)]
     public string LevelCode { get; set; } = string.Empty;

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8WatchRule.cs

@@ -32,8 +32,9 @@ public class AdoS8WatchRule
     [SugarColumn(ColumnName = "expression", Length = 2000, IsNullable = true)]
     public string? Expression { get; set; }
 
+    /// <summary>S8 业务枚举 FOLLOW(关注)/ SERIOUS(严重)。</summary>
     [SugarColumn(ColumnName = "severity", Length = 32)]
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
 
     [SugarColumn(ColumnName = "poll_interval_seconds")]
     public int PollIntervalSeconds { get; set; } = 300;

+ 4 - 11
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8Labels.cs

@@ -15,20 +15,13 @@ public static class S8Labels
         _ => code
     };
 
-    public static string SeverityLabel(string code) => code switch
-    {
-        "CRITICAL" => "紧急",
-        "HIGH" => "高",
-        "MEDIUM" => "中",
-        "LOW" => "低",
-        _ => code
-    };
+    /// <summary>S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:旧值 LOW/MEDIUM/HIGH/CRITICAL 通过
+    /// <see cref="S8SeverityCode.Normalize"/> 归一为 FOLLOW/SERIOUS 后再取 label,业务输出只有「关注 / 严重」。</summary>
+    public static string SeverityLabel(string code) => S8SeverityCode.Label(code);
 
     public static object[] StatusOptions() =>
         new[] { "NEW", "ASSIGNED", "IN_PROGRESS", "PENDING_VERIFICATION", "RESOLVED", "CLOSED", "REJECTED", "ESCALATED" }
             .Select(v => new { value = v, label = StatusLabel(v) }).ToArray<object>();
 
-    public static object[] SeverityOptions() =>
-        new[] { "CRITICAL", "HIGH", "MEDIUM", "LOW" }
-            .Select(v => new { value = v, label = SeverityLabel(v) }).ToArray<object>();
+    public static object[] SeverityOptions() => S8SeverityCode.Options();
 }

+ 53 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8SeverityCode.cs

@@ -0,0 +1,53 @@
+namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
+
+/// <summary>
+/// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:S8 异常 severity 业务枚举。
+/// 当前阶段只保留 FOLLOW(关注)/ SERIOUS(严重)两档;
+/// 旧值 LOW/MEDIUM/HIGH/CRITICAL 仅作为 legacy 兼容输入,新写入必须落 FOLLOW/SERIOUS。
+/// </summary>
+public static class S8SeverityCode
+{
+    public const string Follow = "FOLLOW";
+    public const string Serious = "SERIOUS";
+
+    /// <summary>归一化:旧值/空值/新值 -> FOLLOW / SERIOUS。空值默认 FOLLOW。</summary>
+    public static string Normalize(string? code)
+    {
+        if (string.IsNullOrWhiteSpace(code)) return Follow;
+        return code.Trim().ToUpperInvariant() switch
+        {
+            "FOLLOW"   => Follow,
+            "LOW"      => Follow,
+            "MEDIUM"   => Follow,
+            "SERIOUS"  => Serious,
+            "HIGH"     => Serious,
+            "CRITICAL" => Serious,
+            _          => Follow,
+        };
+    }
+
+    public static bool IsFollow(string? code) => Normalize(code) == Follow;
+    public static bool IsSerious(string? code) => Normalize(code) == Serious;
+
+    /// <summary>新值或旧值合法(用于 legacy 查询参数兼容)。</summary>
+    public static bool IsValid(string? code)
+    {
+        if (string.IsNullOrWhiteSpace(code)) return false;
+        return code.Trim().ToUpperInvariant() switch
+        {
+            "FOLLOW" or "SERIOUS" or "LOW" or "MEDIUM" or "HIGH" or "CRITICAL" => true,
+            _ => false,
+        };
+    }
+
+    public static string Label(string? code) => Normalize(code) switch
+    {
+        Serious => "严重",
+        _       => "关注",
+    };
+
+    public static object[] Options() =>
+        new[] { Follow, Serious }
+            .Select(v => new { value = v, label = Label(v) })
+            .ToArray<object>();
+}

+ 44 - 44
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/S8ExceptionTypeSeedData.cs

@@ -37,69 +37,69 @@ public class S8ExceptionTypeSeedData : ISqlSugarEntitySeedData<Entity.S8.AdoS8Ex
         return new[]
         {
             // ── S1 产销协同 + S7 成品仓储(交付侧) ──
-            T(baseId + 1,  "ORDER_CHANGE",             "订单变更",           S8SceneCode.S1, 60,  "ROLE_ORDER_PLANNER",      "MEDIUM", 100, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
-            T(baseId + 2,  "DELIVERY_DELAY",           "交期延迟",           S8SceneCode.S7, 120, "ROLE_ORDER_PLANNER",      "HIGH",   101, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
-            T(baseId + 3,  "PENDING_SHIPMENT",         "入库待发",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 102, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
-            T(baseId + 16, "ORDER_REVIEW_DELAY",       "订单评审延迟",       S8SceneCode.S1, 120, "ROLE_ORDER_PLANNER",      "MEDIUM", 400, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 1,  "ORDER_CHANGE",             "订单变更",           S8SceneCode.S1, 60,  "ROLE_ORDER_PLANNER",      "FOLLOW", 100, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 2,  "DELIVERY_DELAY",           "交期延迟",           S8SceneCode.S7, 120, "ROLE_ORDER_PLANNER",      "SERIOUS",   101, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 3,  "PENDING_SHIPMENT",         "入库待发",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "FOLLOW", 102, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 16, "ORDER_REVIEW_DELAY",       "订单评审延迟",       S8SceneCode.S1, 120, "ROLE_ORDER_PLANNER",      "FOLLOW", 400, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
 
             // ── S2 制造协同 + S6 生产执行 ──
-            T(baseId + 4,  "EQUIP_FAULT",              "设备异常",           S8SceneCode.S2, 30,  "ROLE_EQUIP_MAINT",        "HIGH",   200, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 4,  "EQUIP_FAULT",              "设备异常",           S8SceneCode.S2, 30,  "ROLE_EQUIP_MAINT",        "SERIOUS",   200, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S8-DEPRECATED-EXCEPTION-TYPE-MIGRATE-1:DIMENSION_DEVIATION/QUALITY_DEFECT 迁 MFG_QUALITY_ABNORMAL
-            T(baseId + 14, "DIMENSION_DEVIATION",      "尺寸超差",           S8SceneCode.S2, 60,  "ROLE_QC",                 "HIGH",   200, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
+            T(baseId + 14, "DIMENSION_DEVIATION",      "尺寸超差",           S8SceneCode.S2, 60,  "ROLE_QC",                 "SERIOUS",   200, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
             // MATERIAL_SHORTAGE 迁 MFG_MATERIAL_ABNORMAL
-            T(baseId + 5,  "MATERIAL_SHORTAGE",        "物料异常",           S8SceneCode.S5, 60,  "ROLE_PRODUCTION_PLANNER", "HIGH",   201, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_MATERIAL_ABNORMAL"),
-            T(baseId + 6,  "QUALITY_DEFECT",           "质量异常",           S8SceneCode.S2, 60,  "ROLE_QC",                 "HIGH",   202, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
+            T(baseId + 5,  "MATERIAL_SHORTAGE",        "物料异常",           S8SceneCode.S5, 60,  "ROLE_PRODUCTION_PLANNER", "SERIOUS",   201, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_MATERIAL_ABNORMAL"),
+            T(baseId + 6,  "QUALITY_DEFECT",           "质量异常",           S8SceneCode.S2, 60,  "ROLE_QC",                 "SERIOUS",   202, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
             // YIELD_DEFICIT 迁 PRODUCTION_QUALITY_ABNORMAL
-            T(baseId + 15, "YIELD_DEFICIT",            "良率不足",           S8SceneCode.S6, 60,  "ROLE_QC",                 "HIGH",   300, ct, enabled: false, remark: "[DEPRECATED] migrated to PRODUCTION_QUALITY_ABNORMAL"),
-            T(baseId + 19, "WORK_ORDER_DELAY",         "工单延期",           S8SceneCode.S6, 60,  "ROLE_PRODUCTION_PLANNER", "HIGH",   403, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 15, "YIELD_DEFICIT",            "良率不足",           S8SceneCode.S6, 60,  "ROLE_QC",                 "SERIOUS",   300, ct, enabled: false, remark: "[DEPRECATED] migrated to PRODUCTION_QUALITY_ABNORMAL"),
+            T(baseId + 19, "WORK_ORDER_DELAY",         "工单延期",           S8SceneCode.S6, 60,  "ROLE_PRODUCTION_PLANNER", "SERIOUS",   403, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
 
             // ── S3 供应协同 + S4 采购执行 + S5 物料仓储 ──
-            T(baseId + 7,  "SUPPLIER_ETA_ISSUE",       "供应商回复交期异常", S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 300, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 8,  "SUPPLIER_SHIP_ISSUE",      "供应商发货异常",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 301, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 7,  "SUPPLIER_ETA_ISSUE",       "供应商回复交期异常", S8SceneCode.S3, 240, "ROLE_PURCHASER",          "FOLLOW", 300, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 8,  "SUPPLIER_SHIP_ISSUE",      "供应商发货异常",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "FOLLOW", 301, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // WH_INBOUND_ISSUE 迁 WAREHOUSE_RECEIPT_ABNORMAL
-            T(baseId + 9,  "WH_INBOUND_ISSUE",         "仓库收货异常",       S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 302, ct, enabled: false, remark: "[DEPRECATED] migrated to WAREHOUSE_RECEIPT_ABNORMAL"),
-            T(baseId + 10, "IQC_ISSUE",                "IQC 检验异常",       S8SceneCode.S3, 120, "ROLE_QC",                 "MEDIUM", 303, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 11, "WH_PUTAWAY_ISSUE",         "仓库上架入库异常",   S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "LOW",    304, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 9,  "WH_INBOUND_ISSUE",         "仓库收货异常",       S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "FOLLOW", 302, ct, enabled: false, remark: "[DEPRECATED] migrated to WAREHOUSE_RECEIPT_ABNORMAL"),
+            T(baseId + 10, "IQC_ISSUE",                "IQC 检验异常",       S8SceneCode.S3, 120, "ROLE_QC",                 "FOLLOW", 303, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 11, "WH_PUTAWAY_ISSUE",         "仓库上架入库异常",   S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "FOLLOW",    304, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // WH_KIT_ISSUE 迁 WORK_ORDER_KITTING_ABNORMAL
-            T(baseId + 12, "WH_KIT_ISSUE",             "仓库工单备料异常",   S8SceneCode.S5, 60,  "ROLE_WH_OUTBOUND",        "MEDIUM", 305, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_KITTING_ABNORMAL"),
+            T(baseId + 12, "WH_KIT_ISSUE",             "仓库工单备料异常",   S8SceneCode.S5, 60,  "ROLE_WH_OUTBOUND",        "FOLLOW", 305, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_KITTING_ABNORMAL"),
             // WH_ISSUE_OUT_ISSUE 迁 WORK_ORDER_ISSUE_ABNORMAL
-            T(baseId + 13, "WH_ISSUE_OUT_ISSUE",       "仓库工单发料异常",   S8SceneCode.S7, 60,  "ROLE_WH_OUTBOUND",        "MEDIUM", 306, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_ISSUE_ABNORMAL"),
-            T(baseId + 17, "PURCHASE_EXECUTION_DELAY", "采购执行延迟",       S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 401, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 18, "PURCHASE_QUALITY_ABNORMAL","采购质量异常",       S8SceneCode.S4, 60,  "ROLE_QC",                 "MEDIUM", 402, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 20, "MATERIAL_STOCK_ABNORMAL",  "库存异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 404, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 13, "WH_ISSUE_OUT_ISSUE",       "仓库工单发料异常",   S8SceneCode.S7, 60,  "ROLE_WH_OUTBOUND",        "FOLLOW", 306, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_ISSUE_ABNORMAL"),
+            T(baseId + 17, "PURCHASE_EXECUTION_DELAY", "采购执行延迟",       S8SceneCode.S4, 120, "ROLE_PURCHASER",          "FOLLOW", 401, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 18, "PURCHASE_QUALITY_ABNORMAL","采购质量异常",       S8SceneCode.S4, 60,  "ROLE_QC",                 "FOLLOW", 402, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 20, "MATERIAL_STOCK_ABNORMAL",  "库存异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "FOLLOW", 404, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
 
             // ── S8-BASELINE-EXCEPTION-TYPE-RULE-TEMPLATE-UPDATE-1(基于 lwb/S1-S7异常类型与三类规则对应表.md)──
             // S1 产销协同(新增 3)
-            T(baseId + 21, "ORDER_DUE_DATE_DELAY",                         "订单交期延迟",           S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   110, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
-            T(baseId + 22, "ORDER_DELIVERY_DELAY_WARNING",                 "订单交付延期预警",       S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "MEDIUM", 111, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
-            T(baseId + 23, "DELIVERY_SATISFACTION_RATE_ABNORMAL",          "交付满足率异常",         S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   112, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 21, "ORDER_DUE_DATE_DELAY",                         "订单交期延迟",           S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "SERIOUS",   110, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 22, "ORDER_DELIVERY_DELAY_WARNING",                 "订单交付延期预警",       S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "FOLLOW", 111, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 23, "DELIVERY_SATISFACTION_RATE_ABNORMAL",          "交付满足率异常",         S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "SERIOUS",   112, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
             // S2 制造协同(新增 4)
-            T(baseId + 24, "WORK_ORDER_COMPLETION_DELAY_WARNING",          "工单完工延期预警",       S8SceneCode.S2, 120, "ROLE_PRODUCTION_PLANNER", "MEDIUM", 210, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 25, "WORK_ORDER_COMPLETION_RATE_ABNORMAL",          "工单完工满足率异常",     S8SceneCode.S2, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   211, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 26, "MFG_MATERIAL_ABNORMAL",                        "制造协同物料异常",       S8SceneCode.S2,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   212, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 27, "MFG_QUALITY_ABNORMAL",                         "制造协同质量异常",       S8SceneCode.S2,  60, "ROLE_QC",                 "HIGH",   213, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 24, "WORK_ORDER_COMPLETION_DELAY_WARNING",          "工单完工延期预警",       S8SceneCode.S2, 120, "ROLE_PRODUCTION_PLANNER", "FOLLOW", 210, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 25, "WORK_ORDER_COMPLETION_RATE_ABNORMAL",          "工单完工满足率异常",     S8SceneCode.S2, 240, "ROLE_PRODUCTION_PLANNER", "SERIOUS",   211, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 26, "MFG_MATERIAL_ABNORMAL",                        "制造协同物料异常",       S8SceneCode.S2,  60, "ROLE_PRODUCTION_PLANNER", "SERIOUS",   212, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 27, "MFG_QUALITY_ABNORMAL",                         "制造协同质量异常",       S8SceneCode.S2,  60, "ROLE_QC",                 "SERIOUS",   213, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S3 供应协同(新增 1)
-            T(baseId + 28, "SUPPLIER_DELIVERY_DELAY_WARNING",              "供应商交付延期预警",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 310, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 28, "SUPPLIER_DELIVERY_DELAY_WARNING",              "供应商交付延期预警",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "FOLLOW", 310, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S4 采购执行(新增 2)
-            T(baseId + 29, "PURCHASE_DELIVERY_ABNORMAL",                   "采购交期异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "HIGH",   410, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 30, "PURCHASE_ARRIVAL_ABNORMAL",                    "采购到货异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 411, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 29, "PURCHASE_DELIVERY_ABNORMAL",                   "采购交期异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "SERIOUS",   410, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 30, "PURCHASE_ARRIVAL_ABNORMAL",                    "采购到货异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "FOLLOW", 411, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S5 物料仓储(新增 7)
-            T(baseId + 31, "WAREHOUSE_RECEIPT_ABNORMAL",                   "仓库收货异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 510, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 32, "WORK_ORDER_KITTING_ABNORMAL",                  "工单备料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 511, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 33, "WORK_ORDER_ISSUE_ABNORMAL",                    "工单发料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 512, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 34, "MATERIAL_KITTING_ABNORMAL",                    "物料齐套异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "HIGH",   513, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 35, "MATERIAL_KITTING_DELAY_WARNING",               "物料齐套延期预警",       S8SceneCode.S5, 120, "ROLE_WH_OUTBOUND",        "MEDIUM", 514, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 36, "INVENTORY_TURNOVER_ABNORMAL",                  "库存周转异常",           S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 515, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
-            T(baseId + 37, "INVENTORY_AMOUNT_LEVEL_ABNORMAL",              "库存金额水位异常",       S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 516, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 31, "WAREHOUSE_RECEIPT_ABNORMAL",                   "仓库收货异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "FOLLOW", 510, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 32, "WORK_ORDER_KITTING_ABNORMAL",                  "工单备料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "FOLLOW", 511, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 33, "WORK_ORDER_ISSUE_ABNORMAL",                    "工单发料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "FOLLOW", 512, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 34, "MATERIAL_KITTING_ABNORMAL",                    "物料齐套异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "SERIOUS",   513, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 35, "MATERIAL_KITTING_DELAY_WARNING",               "物料齐套延期预警",       S8SceneCode.S5, 120, "ROLE_WH_OUTBOUND",        "FOLLOW", 514, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 36, "INVENTORY_TURNOVER_ABNORMAL",                  "库存周转异常",           S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "FOLLOW", 515, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 37, "INVENTORY_AMOUNT_LEVEL_ABNORMAL",              "库存金额水位异常",       S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "FOLLOW", 516, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S6 生产执行(新增 4)
-            T(baseId + 38, "PRODUCTION_MATERIAL_ABNORMAL",                  "生产物料异常",           S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   610, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 39, "PRODUCTION_QUALITY_ABNORMAL",                   "生产质量异常",           S8SceneCode.S6,  60, "ROLE_QC",                 "HIGH",   611, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 40, "PRODUCTION_WORK_ORDER_COMPLETION_DELAY",        "生产工单完工延期",       S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   612, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
-            T(baseId + 41, "PRODUCTION_WORK_ORDER_COMPLETION_RATE_ABNORMAL","生产工单完工满足率异常", S8SceneCode.S6, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   613, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 38, "PRODUCTION_MATERIAL_ABNORMAL",                  "生产物料异常",           S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "SERIOUS",   610, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 39, "PRODUCTION_QUALITY_ABNORMAL",                   "生产质量异常",           S8SceneCode.S6,  60, "ROLE_QC",                 "SERIOUS",   611, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 40, "PRODUCTION_WORK_ORDER_COMPLETION_DELAY",        "生产工单完工延期",       S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "SERIOUS",   612, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 41, "PRODUCTION_WORK_ORDER_COMPLETION_RATE_ABNORMAL","生产工单完工满足率异常", S8SceneCode.S6, 240, "ROLE_PRODUCTION_PLANNER", "SERIOUS",   613, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S7 成品仓储(新增 3)
-            T(baseId + 42, "FINISHED_GOODS_PENDING_SHIPMENT",              "成品待发异常",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 710, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
-            T(baseId + 43, "SHIPMENT_ABNORMAL",                            "出货异常",               S8SceneCode.S7, 120, "ROLE_WH_OUTBOUND",        "HIGH",   711, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
-            T(baseId + 44, "ORDER_DELIVERY_RATE_ABNORMAL",                 "订单交付满足率异常",     S8SceneCode.S7, 240, "ROLE_ORDER_PLANNER",      "HIGH",   712, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 42, "FINISHED_GOODS_PENDING_SHIPMENT",              "成品待发异常",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "FOLLOW", 710, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 43, "SHIPMENT_ABNORMAL",                            "出货异常",               S8SceneCode.S7, 120, "ROLE_WH_OUTBOUND",        "SERIOUS",   711, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 44, "ORDER_DELIVERY_RATE_ABNORMAL",                 "订单交付满足率异常",     S8SceneCode.S7, 240, "ROLE_ORDER_PLANNER",      "SERIOUS",   712, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
         };
     }
 

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/IS8RuleEvaluator.cs

@@ -34,7 +34,7 @@ public sealed class S8RuleHit
     public string RelatedObjectCode { get; set; } = string.Empty;
     public string ExceptionTypeCode { get; set; } = string.Empty;
     public string SceneCode { get; set; } = string.Empty;
-    public string Severity { get; set; } = "MEDIUM";
+    public string Severity { get; set; } = "FOLLOW";
     public string DedupKey { get; set; } = string.Empty;
     public string SourcePayload { get; set; } = "{}";
     public DateTime DetectedAt { get; set; } = DateTime.Now;

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8OutOfRangeRuleEvaluator.cs

@@ -2,6 +2,7 @@ using System.Data;
 using System.Globalization;
 using System.Text.Json;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using SqlSugar;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
@@ -139,7 +140,7 @@ public class S8OutOfRangeRuleEvaluator : IS8RuleEvaluator, ITransient
                 RelatedObjectCode = relatedObjectCode,
                 ExceptionTypeCode = exceptionTypeCode,
                 SceneCode = rule.SceneCode,
-                Severity = string.IsNullOrWhiteSpace(rule.Severity) ? "MEDIUM" : rule.Severity,
+                Severity = S8SeverityCode.Normalize(rule.Severity),
                 DedupKey = dedupKey,
                 SourcePayload = BuildPayload(row, sourceObjectType, sourceObjectId, measured.Value, lower, upper, deviation, direction, exceptionTypeCode),
                 DetectedAt = detectedAt,

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8ShortageRuleEvaluator.cs

@@ -2,6 +2,7 @@ using System.Data;
 using System.Globalization;
 using System.Text.Json;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using SqlSugar;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
@@ -116,7 +117,7 @@ public class S8ShortageRuleEvaluator : IS8RuleEvaluator, ITransient
                 RelatedObjectCode = relatedObjectCode,
                 ExceptionTypeCode = parameters.ExceptionTypeCode!,
                 SceneCode = rule.SceneCode,
-                Severity = string.IsNullOrWhiteSpace(rule.Severity) ? "MEDIUM" : rule.Severity,
+                Severity = S8SeverityCode.Normalize(rule.Severity),
                 DedupKey = dedupKey,
                 SourcePayload = BuildPayload(row, sourceObjectType, sourceObjectId, target.Value, actual.Value, shortage, ratio, parameters),
                 DetectedAt = detectedAt,

+ 2 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8TimeoutRuleEvaluator.cs

@@ -2,6 +2,7 @@ using System.Data;
 using System.Globalization;
 using System.Text.Json;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using SqlSugar;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
@@ -108,7 +109,7 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
                 RelatedObjectCode = relatedObjectCode,
                 ExceptionTypeCode = parameters.ExceptionTypeCode!,
                 SceneCode = rule.SceneCode,
-                Severity = string.IsNullOrWhiteSpace(rule.Severity) ? "MEDIUM" : rule.Severity,
+                Severity = S8SeverityCode.Normalize(rule.Severity),
                 DedupKey = dedupKey,
                 SourcePayload = BuildPayload(row, sourceObjectType, sourceObjectId, due.Value, status, parameters),
                 DetectedAt = detectedAt,

+ 11 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DashboardService.cs

@@ -48,7 +48,9 @@ public class S8DashboardService : ITransient
         var timeout    = await q.CountAsync(x => x.TimeoutFlag);
         var closed     = await q.CountAsync(x => x.Status == "CLOSED");
         var todayNew   = await q.CountAsync(x => x.CreatedAt >= DateTime.Today);
-        var critical   = await q.CountAsync(x => x.Severity == "CRITICAL");
+        // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:critical 字段名保留以维持外部 KPI;语义切为「严重」(SERIOUS)。
+        var seriousList = await q.Select(x => x.Severity).ToListAsync();
+        var critical   = seriousList.Count(s => S8SeverityCode.IsSerious(s));
 
         var closureRate = total > 0 ? Math.Round(closed * 100.0 / total, 1) : 0.0;
 
@@ -123,8 +125,9 @@ public class S8DashboardService : ITransient
             byScene = list
                 .GroupBy(x => x.SceneCode)
                 .Select(g => new { key = g.Key, count = g.Count() }),
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:bySeverity 归一为 FOLLOW/SERIOUS 两桶。
             bySeverity = list
-                .GroupBy(x => x.Severity)
+                .GroupBy(x => S8SeverityCode.Normalize(x.Severity))
                 .Select(g => new { key = g.Key, count = g.Count() }),
             byDept = list
                 .GroupBy(x => x.ResponsibleDeptId)
@@ -145,6 +148,8 @@ public class S8DashboardService : ITransient
             // S8-PROCESS-NODE-MODULE-CODE-ALIGNMENT-EXEC-1:当前阶段「流程环节」按 module_code 聚合
             // (process_node_code 已挂起,留给未来更细流程节点)。
             // module_code 已经过 S1-S7 过滤(见 line 87 Where(S8ModuleCode.All.Contains(...))),无需再 NULL 兜底。
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:byProcess 增加 followCount/seriousCount 两段,
+            // 供前端「流程环节」黄红堆叠柱使用;count = follow + serious。排序按 S1-S7 自然字典序固定。
             byProcess = list
                 .GroupBy(x => x.ModuleCode)
                 .Select(g => new
@@ -152,7 +157,10 @@ public class S8DashboardService : ITransient
                     key = g.Key,
                     nodeName = processDict.GetValueOrDefault(g.Key, g.Key),
                     count = g.Count(),
-                }),
+                    followCount = g.Count(x => S8SeverityCode.IsFollow(x.Severity)),
+                    seriousCount = g.Count(x => S8SeverityCode.IsSerious(x.Severity)),
+                })
+                .OrderBy(r => r.key),
             byObject = list
                 // S8-DASHBOARD-BYOBJECT-NULL-BUCKET-1:不再过滤 NULL,与 dim-trends 口径统一;
                 // related_object_code 为空时归入"未关联"桶。

+ 3 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DictionaryService.cs

@@ -18,10 +18,9 @@ public class S8DictionaryService : ITransient
             },
             "S8_EXCEPTION_SEVERITY" => new[]
             {
-                new { value = "CRITICAL", label = "紧急" },
-                new { value = "HIGH", label = "高" },
-                new { value = "MEDIUM", label = "中" },
-                new { value = "LOW", label = "低" },
+                // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举只保留 FOLLOW/SERIOUS。
+                new { value = "FOLLOW",  label = "关注" },
+                new { value = "SERIOUS", label = "严重" },
             },
             "S8_SOURCE_TYPE" => new[]
             {

+ 4 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs

@@ -43,7 +43,10 @@ public class S8ExceptionService : ITransient
             .WhereIF(!string.IsNullOrWhiteSpace(q.Status), (e, sc, wr) => e.Status == q.Status)
             .WhereIF(string.IsNullOrWhiteSpace(q.Status) && q.StatusBucket == "pending",
                 (e, sc, wr) => pendingStatuses.Contains(e.Status))
-            .WhereIF(!string.IsNullOrWhiteSpace(q.Severity), (e, sc, wr) => e.Severity == q.Severity)
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:legacy 兼容 — q.Severity 传 LOW/MEDIUM/HIGH/CRITICAL 时
+            // 通过 Normalize 映射为 FOLLOW/SERIOUS 再过滤。
+            .WhereIF(!string.IsNullOrWhiteSpace(q.Severity),
+                (e, sc, wr) => e.Severity == S8SeverityCode.Normalize(q.Severity))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SceneCode), (e, sc, wr) => e.SceneCode == q.SceneCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.ModuleCode), (e, sc, wr) => e.ModuleCode == q.ModuleCode)
             // S8-DASHBOARD-DATA-ALIGN-S1S7-1:看板明细表传 OnlyS1S7Modules=true 时与 KPI 口径对齐;

+ 15 - 16
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ManualReportService.cs

@@ -11,10 +11,12 @@ namespace Admin.NET.Plugin.AiDOP.Service.S8;
 
 public class S8ManualReportService : ITransient
 {
-    // 合法严重度白名单(与 GetFormOptionsAsync.severities 同源);前端/后端默认值 MEDIUM。
-    private static readonly HashSet<string> AllowedSeverities = new(StringComparer.Ordinal)
+    // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举只保留 FOLLOW/SERIOUS。
+    // 旧值 LOW/MEDIUM/HIGH/CRITICAL 仍可作为兼容输入(接收后由 S8SeverityCode.Normalize 归一)。
+    private static readonly HashSet<string> AllowedSeverities = new(StringComparer.OrdinalIgnoreCase)
     {
-        "CRITICAL", "HIGH", "MEDIUM", "LOW"
+        "FOLLOW", "SERIOUS",
+        "LOW", "MEDIUM", "HIGH", "CRITICAL", // legacy compat
     };
 
     // S8-PROCESS-NODE-S1S7-ALIGN-1:process_node_code 当前阶段对齐 S1-S7 订单主流程。
@@ -171,13 +173,8 @@ public class S8ManualReportService : ITransient
         return new
         {
             scenes,
-            severities = new[]
-            {
-                new { value = "CRITICAL", label = "紧急" },
-                new { value = "HIGH", label = "高" },
-                new { value = "MEDIUM", label = "中" },
-                new { value = "LOW", label = "低" }
-            },
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举 FOLLOW/SERIOUS 两档。
+            severities = S8SeverityCode.Options(),
             departments,
             lines,
             materials = Array.Empty<object>()
@@ -189,10 +186,12 @@ public class S8ManualReportService : ITransient
         if (string.IsNullOrWhiteSpace(dto.Title)) throw new S8BizException("标题必填");
         if (string.IsNullOrWhiteSpace(dto.SceneCode)) throw new S8BizException("场景必填");
 
-        // 严重度白名单校验:空值兜底为 MEDIUM;非法值直接拒绝,避免 P3 等错位写入。
-        var severity = string.IsNullOrWhiteSpace(dto.Severity) ? "MEDIUM" : dto.Severity.Trim();
-        if (!AllowedSeverities.Contains(severity))
-            throw new S8BizException($"严重度 {severity} 非法,仅允许 CRITICAL/HIGH/MEDIUM/LOW");
+        // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:白名单接受新值(FOLLOW/SERIOUS)+ 旧值兼容;
+        // 通过 Normalize 写入 DB 一律为 FOLLOW/SERIOUS。
+        var rawSeverity = string.IsNullOrWhiteSpace(dto.Severity) ? S8SeverityCode.Follow : dto.Severity.Trim();
+        if (!AllowedSeverities.Contains(rawSeverity))
+            throw new S8BizException($"严重度 {rawSeverity} 非法,仅允许 FOLLOW/SERIOUS");
+        var severity = S8SeverityCode.Normalize(rawSeverity);
 
         // 提报人以服务端登录上下文为准,忽略前端传入;未登录上下文落 null。
         var currentUserId = _userManager.UserId > 0 ? _userManager.UserId : (long?)null;
@@ -309,7 +308,7 @@ public class S8ManualReportService : ITransient
             // 首版自动监控建单来源标识(字符串值,先不抽常量类)。
             SourceType = "AUTO_WATCH",
             Status = "NEW",
-            Severity = string.IsNullOrWhiteSpace(hit.Severity) ? "MEDIUM" : hit.Severity,
+            Severity = S8SeverityCode.Normalize(hit.Severity),
             PriorityScore = 0,
             PriorityLevel = "P3",
             // 首版兜底口径:Hit 未提供部门时置 0 仅为保证“能建成标准异常单并进入主链”,
@@ -395,7 +394,7 @@ public class S8ManualReportService : ITransient
             SceneCode = effectiveScene,
             SourceType = "AUTO_WATCH",
             Status = "NEW",
-            Severity = string.IsNullOrWhiteSpace(hit.Severity) ? "MEDIUM" : hit.Severity,
+            Severity = S8SeverityCode.Normalize(hit.Severity),
             PriorityScore = 0,
             PriorityLevel = "P3",
             OccurrenceDeptId = hit.OccurrenceDeptId ?? 0,

+ 13 - 9
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -54,9 +54,11 @@ public class S8MonitoringService : ITransient
         {
             var rows = events.Where(e => e.ModuleCode == mc).ToList();
             var unclosed = rows.Where(e => e.Status != "CLOSED").ToList();
-            var red    = unclosed.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH");
-            var yellow = unclosed.Count(e => e.Severity == "MEDIUM");
-            var green  = unclosed.Count(e => e.Severity == "LOW");
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:red=SERIOUS / yellow=FOLLOW;
+            // green 不再来自异常 severity(异常表无「正常」桶),保留 0 兜底,绿色由 useS8StageConfig 兜底。
+            var red    = unclosed.Count(e => S8SeverityCode.IsSerious(e.Severity));
+            var yellow = unclosed.Count(e => S8SeverityCode.IsFollow(e.Severity));
+            var green  = 0;
             var closed = rows.Count(e => e.Status == "CLOSED");
             var total  = rows.Count;
             return new AdoS8ModuleOrderSummary
@@ -237,9 +239,10 @@ public class S8MonitoringService : ITransient
                 SceneCode   = g.Key.sc,
                 SceneLabel  = S8SceneCode.Label(g.Key.sc),
                 Total   = g.Count(),
-                Red     = g.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH"),
-                Yellow  = g.Count(e => e.Severity == "MEDIUM"),
-                Green   = g.Count(e => e.Severity == "LOW"),
+                // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:red=SERIOUS / yellow=FOLLOW;green=0(异常表无正常桶)。
+                Red     = g.Count(e => S8SeverityCode.IsSerious(e.Severity)),
+                Yellow  = g.Count(e => S8SeverityCode.IsFollow(e.Severity)),
+                Green   = 0,
                 Timeout = g.Count(e => e.TimeoutFlag && e.Status != "CLOSED")
             })
             // 按 S8ModuleCode.All 顺序排列
@@ -249,9 +252,10 @@ public class S8MonitoringService : ITransient
         return new AdoS8MonitoringSummaryDto
         {
             Total   = raw.Count,
-            Red     = raw.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH"),
-            Yellow  = raw.Count(e => e.Severity == "MEDIUM"),
-            Green   = raw.Count(e => e.Severity == "LOW"),
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:red=SERIOUS / yellow=FOLLOW;green=0。
+            Red     = raw.Count(e => S8SeverityCode.IsSerious(e.Severity)),
+            Yellow  = raw.Count(e => S8SeverityCode.IsFollow(e.Severity)),
+            Green   = 0,
             Timeout = raw.Count(e => e.TimeoutFlag && e.Status != "CLOSED"),
             ByModule = byModule
         };

+ 3 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8NotificationLayerResolver.cs

@@ -1,4 +1,5 @@
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using Admin.NET.Plugin.ApprovalFlow.Service;
 using Microsoft.Extensions.Logging;
 
@@ -84,7 +85,7 @@ public class S8NotificationLayerResolver : ITransient
 				.Where(x => x.TenantId == input.TenantId
 					&& x.FactoryId == input.FactoryId
 					&& x.SceneCode == input.SceneCode
-					&& x.Severity == input.Severity)
+					&& x.Severity == S8SeverityCode.Normalize(input.Severity))
 				.ToListAsync();
 
 			// baseline fallback:未命中精确租户/工厂 → 尝试 tenant=0/factory=0 全局基线
@@ -95,7 +96,7 @@ public class S8NotificationLayerResolver : ITransient
 					.Where(x => x.TenantId == 0
 						&& x.FactoryId == 0
 						&& x.SceneCode == input.SceneCode
-						&& x.Severity == input.Severity)
+						&& x.Severity == S8SeverityCode.Normalize(input.Severity))
 					.ToListAsync();
 				if (layers.Count > 0)
 					_logger.LogInformation("S8LayerDispatch: matched {N} baseline (tenant=0/factory=0) layer rows", layers.Count);

+ 5 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8NotificationLayerService.cs

@@ -1,5 +1,6 @@
 using Admin.NET.Core;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
+using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8;
 
@@ -39,6 +40,8 @@ public class S8NotificationLayerService : ITransient
             throw new S8BizException("目标角色必填");
         if (string.IsNullOrWhiteSpace(body.NotifyChannel))
             throw new S8BizException("通知渠道至少选一个");
+        // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:归一为 FOLLOW/SERIOUS 后再校验/写入。
+        body.Severity = S8SeverityCode.Normalize(body.Severity);
         var exists = await _rep.AsQueryable()
             .AnyAsync(x => x.TenantId == body.TenantId && x.FactoryId == body.FactoryId &&
                            x.SceneCode == body.SceneCode && x.Severity == body.Severity && x.LevelCode == body.LevelCode);
@@ -57,6 +60,8 @@ public class S8NotificationLayerService : ITransient
             throw new S8BizException("目标角色必填");
         if (string.IsNullOrWhiteSpace(body.NotifyChannel))
             throw new S8BizException("通知渠道至少选一个");
+        // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:归一为 FOLLOW/SERIOUS 后再校验/写入。
+        body.Severity = S8SeverityCode.Normalize(body.Severity);
         var exists = await _rep.AsQueryable()
             .AnyAsync(x => x.Id != id && x.TenantId == body.TenantId && x.FactoryId == body.FactoryId &&
                            x.SceneCode == body.SceneCode && x.Severity == body.Severity && x.LevelCode == body.LevelCode);

+ 4 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8WatchSchedulerService.cs

@@ -1618,7 +1618,8 @@ public class S8WatchSchedulerService : ITransient
         try
         {
             var sceneCode = string.IsNullOrWhiteSpace(entity.SceneCode) ? "S8_DEMO_DEFAULT" : entity.SceneCode;
-            var severity = string.IsNullOrWhiteSpace(entity.Severity) ? "MEDIUM" : entity.Severity;
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:写入前 Normalize,落 FOLLOW/SERIOUS。
+            var severity = S8SeverityCode.Normalize(entity.Severity);
             var content =
                 $"异常 {entity.ExceptionCode}:{entity.Title}(场景 {sceneCode},严重度 {severity}" +
                 (string.IsNullOrWhiteSpace(entity.SourceRuleCode) ? "" : $",规则 {entity.SourceRuleCode}") + ")";
@@ -1662,7 +1663,8 @@ public class S8WatchSchedulerService : ITransient
                 return;
             }
             var sceneCode = string.IsNullOrWhiteSpace(entity.SceneCode) ? "S8_DEMO_DEFAULT" : entity.SceneCode;
-            var severity = string.IsNullOrWhiteSpace(entity.Severity) ? "MEDIUM" : entity.Severity;
+            // S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:写入前 Normalize,落 FOLLOW/SERIOUS。
+            var severity = S8SeverityCode.Normalize(entity.Severity);
             var title = $"【已恢复】{entity.ExceptionCode}";
             var content =
                 $"异常 {entity.ExceptionCode} 已恢复,场景 {sceneCode},严重度 {severity}" +