Skip to main content

公司場景 Pattern

Pattern 1: 告警規則引擎

場景:不同設備、不同客戶的告警條件不同。現場工程師需要自行調整閾值(溫度 > X 且壓力 < Y 觸發告警),不需要工程師改程式碼重新部署。

規則定義(存在 DB,UI 可編輯)

[{
"WorkflowName": "AlarmRules_CMP01",
"Rules": [
{
"RuleName": "OverTemperature",
"Expression": "Temperature > 80",
"SuccessEvent": "Critical",
"ErrorMessage": "溫度 $(Temperature)°C 超過上限 80°C",
"Properties": { "Category": "Temperature", "Priority": 1 }
},
{
"RuleName": "HighPressure",
"Expression": "Pressure > 150 OR Pressure < 30",
"SuccessEvent": "Warning",
"ErrorMessage": "壓力 $(Pressure) kPa 超出安全範圍 (30-150)",
"Properties": { "Category": "Pressure", "Priority": 2 }
},
{
"RuleName": "CommunicationDegraded",
"Expression": "SuccessRate < 0.95 AND SampleCount > 100",
"SuccessEvent": "Warning",
"ErrorMessage": "通訊成功率 $(SuccessRate) 低於 95%",
"Properties": { "Category": "Communication", "Priority": 3 }
},
{
"RuleName": "PumpDryRun",
"LocalParams": [
{ "Name": "isPumpRunning", "Expression": "PumpStatus == 1" },
{ "Name": "noFlow", "Expression": "FlowRate < 0.5" }
],
"Expression": "isPumpRunning AND noFlow",
"SuccessEvent": "Critical",
"ErrorMessage": "泵浦乾轉:運轉中但流量不足"
}
]
}]

告警服務實作

public class DynamicAlarmService
{
private readonly RulesEngine.RulesEngine _rulesEngine;
private readonly IAlarmRepository _alarmRepository;
private readonly ILogger<DynamicAlarmService> _logger;

public DynamicAlarmService(
IRuleRepository ruleRepository,
IAlarmRepository alarmRepository,
ILogger<DynamicAlarmService> logger)
{
_alarmRepository = alarmRepository;
_logger = logger;

// 從 DB 載入規則
var workflows = ruleRepository.GetAllWorkflows();
_rulesEngine = new RulesEngine.RulesEngine(workflows.ToArray());
}

public async Task EvaluateAsync(string deviceId, SensorSnapshot snapshot)
{
var workflowName = $"AlarmRules_{deviceId}";
var results = await _rulesEngine.ExecuteAllRulesAsync(workflowName, snapshot);

foreach (var result in results.Where(r => r.IsSuccess))
{
var severity = Enum.Parse<AlarmSeverity>(result.Rule.SuccessEvent);
var category = result.Rule.Properties["Category"]?.ToString();

_logger.LogWarning(
"Alarm triggered: {RuleName} on {DeviceId} — {Severity}",
result.Rule.RuleName, deviceId, severity);

await _alarmRepository.RaiseAsync(new Alarm
{
DeviceId = deviceId,
RuleName = result.Rule.RuleName,
Severity = severity,
Category = category,
Message = result.Rule.ErrorMessage
});
}
}

/// <summary>
/// 規則變更時(UI 編輯後),重新載入
/// </summary>
public void ReloadRules(Workflow[] workflows)
{
_rulesEngine.ClearWorkflows();
_rulesEngine.AddOrUpdateWorkflow(workflows);
_logger.LogInformation("Alarm rules reloaded: {Count} workflows", workflows.Length);
}
}

Pattern 2: MES 整合判斷

場景:不同客戶的 MES 系統有不同的資料格式和判斷邏輯。用 RulesEngine 讓每個客戶的整合邏輯可配置,不需要為每家 MES 寫一套程式碼。

[{
"WorkflowName": "MES_Validation_Walton",
"Rules": [
{
"RuleName": "ValidateLotId",
"Expression": "LotId != null AND LotId.Length == 12 AND LotId.StartsWith(\"WLT\")",
"ErrorMessage": "Walton Lot ID 必須為 12 碼且以 WLT 開頭"
},
{
"RuleName": "ValidateRecipeMatch",
"Expression": "MesRecipeId == EquipmentRecipeId",
"ErrorMessage": "MES Recipe $(MesRecipeId) 與設備 Recipe $(EquipmentRecipeId) 不匹配"
},
{
"RuleName": "ValidateOperator",
"Expression": "OperatorId != null AND IsOperatorCertified == true",
"ErrorMessage": "操作員 $(OperatorId) 未通過認證"
}
]
},
{
"WorkflowName": "MES_Validation_Jope",
"Rules": [
{
"RuleName": "ValidateLotId",
"Expression": "LotId != null AND LotId.Length >= 8",
"ErrorMessage": "Jope Lot ID 至少 8 碼"
},
{
"RuleName": "CheckMaintenanceWindow",
"Expression": "IsInMaintenanceWindow == false",
"ErrorMessage": "設備在維護時段,不允許投產"
}
]
}]
public class MesIntegrationService
{
private readonly RulesEngine.RulesEngine _rulesEngine;

public async Task<ValidationResult> ValidateLotStartAsync(
string customerId, LotStartRequest request)
{
var workflowName = $"MES_Validation_{customerId}";
var results = await _rulesEngine.ExecuteAllRulesAsync(workflowName, request);

var failures = results
.Where(r => !r.IsSuccess)
.Select(r => r.Rule.ErrorMessage)
.ToList();

return new ValidationResult
{
IsValid = failures.Count == 0,
Errors = failures
};
}
}

Pattern 3: Recipe 進階條件(未來擴充)

場景:目前 EndConditionType 提供三種固定類型(時間、計數、條件)。如果未來需要支援任意複合條件,可以用 RulesEngine 表達式:

{
"WorkflowName": "RecipeEndCondition",
"Rules": [{
"RuleName": "ComplexEndCondition",
"Expression": "Concentration >= TargetConcentration AND pH >= 6.5 AND pH <= 7.5 AND ElapsedMinutes >= MinProcessTime",
"SuccessEvent": "EndProcess"
}]
}
public class RecipeStepExecutor
{
private readonly RulesEngine.RulesEngine _rulesEngine;

public async Task<bool> ShouldEndStepAsync(RecipeStep step, ProcessState state)
{
// 固定條件用原本的 enum
if (step.EndConditionType != EndConditionType.Expression)
{
return EvaluateFixedCondition(step, state);
}

// 動態表達式用 RulesEngine
var input = new
{
state.Concentration,
step.TargetConcentration,
state.pH,
ElapsedMinutes = state.Elapsed.TotalMinutes,
step.MinProcessTime
};

var results = await _rulesEngine.ExecuteAllRulesAsync(
"RecipeEndCondition", input);

return results.Any(r => r.IsSuccess && r.Rule.SuccessEvent == "EndProcess");
}
}
何時引入

這是未來擴充的方向。目前三種 EndConditionType 夠用時,不需要提前引入 RulesEngine 增加複雜度。等到確實有客戶需求要求更靈活的結束條件時再啟用。


Pattern 4: 資料品質檢查

場景:感測器數據在寫入資料庫前需要驗證合理性。規則隨設備校正狀態調整。

[{
"WorkflowName": "DataQuality_TemperatureSensor",
"Rules": [
{
"RuleName": "PhysicalRange",
"Expression": "Value >= -50 AND Value <= 500",
"ErrorMessage": "溫度 $(Value)°C 超出物理量程 (-50 ~ 500)"
},
{
"RuleName": "RateOfChange",
"Expression": "Math.Abs(Value - PreviousValue) < 50",
"ErrorMessage": "溫度變化率異常:$(Value) → $(PreviousValue),差值超過 50°C"
},
{
"RuleName": "SensorOnline",
"Expression": "TimeSinceLastUpdate.TotalSeconds < 30",
"ErrorMessage": "感測器超過 30 秒無更新"
}
]
}]
public class DataQualityService
{
private readonly RulesEngine.RulesEngine _rulesEngine;
private readonly ILogger<DataQualityService> _logger;

public async Task<bool> ValidateReadingAsync(
string sensorType, SensorReading reading, SensorReading? previous)
{
var input = new
{
reading.Value,
PreviousValue = previous?.Value ?? reading.Value,
TimeSinceLastUpdate = DateTime.UtcNow - (previous?.Timestamp ?? DateTime.UtcNow)
};

var workflowName = $"DataQuality_{sensorType}";
var results = await _rulesEngine.ExecuteAllRulesAsync(workflowName, input);

var failures = results.Where(r => !r.IsSuccess).ToList();
if (failures.Any())
{
_logger.LogWarning(
"Data quality check failed for {SensorType}: {Failures}",
sensorType,
string.Join("; ", failures.Select(f => f.Rule.ErrorMessage)));
return false;
}

return true;
}
}

DI 整合

public static class RulesEngineServiceExtensions
{
public static IServiceCollection AddRulesEngine(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddSingleton(sp =>
{
var ruleRepo = sp.GetRequiredService<IRuleRepository>();
var workflows = ruleRepo.GetAllWorkflows();

var settings = new ReSettings
{
EnableExceptionAsErrorMessage = true,
CustomActions = new Dictionary<string, Func<ActionBase>>
{
{ "RaiseAlarm", () => sp.GetRequiredService<RaiseAlarmAction>() }
}
};

return new RulesEngine.RulesEngine(workflows.ToArray(), settings);
});

services.AddTransient<RaiseAlarmAction>();
services.AddTransient<DynamicAlarmService>();
services.AddTransient<DataQualityService>();

return services;
}
}

規則版本控制與審計(FDA Part 11)

對於合規場景,規則的變更需要完整的審計軌跡:

public class AuditableRuleRepository : IRuleRepository
{
private readonly IDbContext _db;
private readonly ILogger<AuditableRuleRepository> _logger;

public async Task UpdateRuleAsync(
string workflowName,
string ruleName,
string newExpression,
string modifiedBy)
{
var existing = await _db.Rules
.FirstAsync(r => r.WorkflowName == workflowName && r.RuleName == ruleName);

// 記錄變更歷史
await _db.RuleAuditLogs.AddAsync(new RuleAuditLog
{
WorkflowName = workflowName,
RuleName = ruleName,
OldExpression = existing.Expression,
NewExpression = newExpression,
ModifiedBy = modifiedBy,
ModifiedAt = DateTime.UtcNow,
Reason = $"Rule updated by {modifiedBy}"
});

existing.Expression = newExpression;
existing.Version++;
await _db.SaveChangesAsync();

_logger.LogWarning(
"Rule {RuleName} in {WorkflowName} modified by {ModifiedBy}: {OldExpr} → {NewExpr}",
ruleName, workflowName, modifiedBy,
existing.Expression, newExpression);
}
}

延伸閱讀