Rules Engine Framework Design
Document Type: Rule Design (Tier 2) Domain: RULES-ENGINE Domain Character: Algorithmic SRS Reference: rules.md Status: Draft Last Updated: 2026-01-25
1. Overview
1.0 Rule Summary
| Property | Value |
|---|---|
| Intent | Orchestrate the execution of PCR analysis rules by controlling which rules apply to a given context, the sequence in which they execute, and how errors affect analysis flow. |
| Inputs | Well observations, Rule Mappings (role/target/specimen combinations), Rule Setup (precedence), Error Codes configuration |
| Outputs | Analysis results with error/warning codes applied to wells |
| Precedence | Framework layer - governs all rule execution order |
1.1 Purpose
The Rules Engine is the infrastructure layer that governs all PCR analysis rule execution. It does not define what individual rules calculate or validate; those are specified in separate rule design documents. This framework ensures deterministic, configurable rule execution across all analysis contexts.
The Rules Engine:
- Resolves which rules apply based on role/target/specimen mapping
- Executes rules in ascending precedence order
- Handles blocking vs. non-blocking errors
- Manages error well filtering
- Provides the rule interface contract
1.2 Requirements Covered
| REQ ID | Title | Priority | Complexity |
|---|---|---|---|
| REQ-RULES-ENGINE-001 | Conditional Rule Execution Based on Mapping | Must | Medium |
| REQ-RULES-ENGINE-002 | Sequential Rule Execution Order | Must | Medium |
| REQ-RULES-ENGINE-003 | Error/Warning Flow Control | Must | High |
1.3 Constraints
Tier 2 Constraint: This document describes the framework architecture, rule interface contract, and execution patterns. Individual rule algorithms are documented in their respective SDS documents.
1.4 Dependencies
| Direction | Component | Purpose |
|---|---|---|
| Consumes | Wells & Observations | PCR data to analyze |
| Rule Mappings | Which rules apply to which contexts | |
| Rule Setup | Execution order (precedence) | |
| Error Codes | Error behavior configuration | |
| Produces | Analysis Results | Modified well data |
| Error/Warning Codes | Applied to wells |
2. Component Architecture
2.1 Component Diagram
2.2 Component Responsibilities
| Component | File | Responsibility | REQ Trace |
|---|---|---|---|
Analyzer | app/Analyzer/Analyzer.php | Main orchestrator - initiates analysis, calls rule execution | All |
EloquentConfigurationRepository | app/Analyzer/EloquentConfigurationRepository.php | Retrieves rules ordered by precedence, caches configuration | ENGINE-001, ENGINE-002 |
Rule (Model) | app/Rule.php | Rule configuration - precedence, type, programmatic name, error well flag | ENGINE-002 |
RuleMapping (Model) | app/RuleMapping.php | Maps rules to role/target/specimen combinations | ENGINE-001 |
AnalyzerRuleContract | app/Analyzer/Contracts/AnalyzerRuleContract.php | Interface contract all rules must implement | All |
Well | app/Analyzer/Well.php | Executes individual rule on observation, handles error prevention | ENGINE-003 |
ErrorCode (Model) | app/ErrorCode.php | Defines error behavior including "Does prevent Analyse" | ENGINE-003 |
3. Data Design
3.1 Entities
| Entity | Owner | Read/Write | Fields Used |
|---|---|---|---|
rules | KITCFG | Read | id, title, programmatic_rule_name, type, precedence, is_allow_error_wells |
rule_mappings | KITCFG | Read | rule_id, role_id, target_id, specimen_id, is_strict |
error_codes | KITCFG | Read | error_code, does_prevent_analyse, lims_status |
wells | RUNRPT | Read/Write | All observation fields, error_code, lims_status |
observations | RUNRPT | Read/Write | All classification/CT/quantity fields, problems |
See Database Reference for full schema.
3.2 Rule Configuration Structure
interface Rule {
id: number;
title: string;
programmatic_rule_name: string; // Maps to class name
type: RuleType; // Parse|Import|Reanalysis|General|...
precedence: number; // Execution order (lower = earlier)
is_allow_error_wells: boolean; // Execute on wells with blocking errors?
site_id: number;
}
enum RuleType {
Parse = 1,
Import = 2,
Reanalysis = 3,
General = 4,
Reporting = 5,
Observation = 6,
ControlCheck = 7,
SampleResult = 8,
ApplyControls = 9,
SampleCheck = 10
}
interface RuleMapping {
id: number;
rule_id: number;
role_id: number; // Role (e.g., Patient, PEC, NEC)
target_id: number; // Target (e.g., COVID, IC)
specimen_id: number; // Specimen type (optional)
is_strict: boolean; // Strict matching required
}
interface ErrorCode {
id: number;
error_code: string; // e.g., "CLSDISC_WELL"
does_prevent_analyse: boolean; // Blocks further analysis?
lims_status: string; // LIMS status to apply
type: 'error' | 'warning' | 'information';
}
3.3 Execution Context
interface AnalysisContext {
wells: WellCollection;
runMixes: Collection<RunMix>;
runTargets: RunTargetsCollection;
previousPatientWells: PatientWellCollection;
previousPecWells: PecWellCollection;
config: ConfigurationRepository;
}
interface RuleExecutionContext {
config: ConfigurationRepository;
observation: AnalyzableObservation;
runMix: RunMix;
runTarget: RunTarget;
well: AnalyzableWell;
wells: WellCollection;
previousPatientWells: PatientWellCollection;
previousPecWells: PecWellCollection;
runTargets: RunTargetsCollection;
}
4. Interface Design
4.1 Rule Interface Contract
All rules must implement AnalyzerRuleContract:
interface AnalyzerRuleContract
{
public function handle(
ConfigurationRepository $config,
AnalyzableObservation $observation,
RunMix $runMix,
RunTarget $runTarget,
AnalyzableWell $well,
WellCollection $wells,
PatientWellCollection $previousPatientWells,
PecWellCollection $previousPecWells,
RunTargetsCollection $runTargets,
): void;
}
| Parameter | Type | Purpose |
|---|---|---|
$config | ConfigurationRepository | Access to all kit configuration |
$observation | AnalyzableObservation | Current observation being analyzed |
$runMix | RunMix | Mix context for the well |
$runTarget | RunTarget | Target context for the observation |
$well | AnalyzableWell | Well containing the observation |
$wells | WellCollection | All wells in current run |
$previousPatientWells | PatientWellCollection | Historical wells for patient |
$previousPecWells | PecWellCollection | Historical PEC wells |
$runTargets | RunTargetsCollection | All targets in current run |
4.2 Rule Registration Mechanism
Rules are registered through the Rule model and resolved dynamically:
// Rule.php - toAnalyzerRule()
public function toAnalyzerRule(): ?AnalyzerRuleContract
{
$analyzerRuleName = 'App\Analyzer\Rules\\'
. Str::studly(Str::title($this->programmatic_rule_name))
. 'Rule';
if (! class_exists($analyzerRuleName)) {
logger(['class does not exists' => $analyzerRuleName]);
return null;
}
return new $analyzerRuleName();
}
Naming Convention:
| programmatic_rule_name | Class Name | File Path |
|---|---|---|
WDCLS | WdclsRule | Analyzer/Rules/WdclsRule.php |
COMBINED_OUTCOME | CombinedOutcomeRule | Analyzer/Rules/CombinedOutcomeRule.php |
LINEAR_REGRESSION_VALIDATION | LinearRegressionValidationRule | Analyzer/Rules/LinearRegressionValidationRule.php |
4.3 Internal APIs
| Method | Class | Purpose |
|---|---|---|
getRulesForPrecedenceOrder() | ConfigurationRepository | Get rules ordered by precedence for given context |
execute() | Rule | Execute rule against all applicable wells |
executeRule() | Well | Execute specific rule on well's observations |
hasErrorWhichPreventAnalyse() | Well | Check if well has blocking error |
getErrorByCode() | ConfigurationRepository | Retrieve error code configuration |
5. Behavioral Design
5.1 Rule Mapping Resolution (REQ-RULES-ENGINE-001)
Algorithm: Resolve Applicable Rules
Inputs:
- roleTargetSpecimenCombinations: Collection - Unique (role, target, specimen) tuples from wells
- includeReanalyzeRules: boolean - Whether to include reanalysis-type rules
Outputs:
- rules: Collection<Rule> - Ordered list of applicable rules
Assumptions:
- Rules and mappings are loaded from database
- Site-scoping is applied
Steps:
1. Query rules with mappings matching any of the roleTargetSpecimenCombinations
2. Filter by site_id if site-scoped
3. Exclude Reanalysis type rules unless includeReanalyzeRules is true
4. Order by precedence ascending
5. Apply special Westgard ordering (WGInError before Westgard errors)
6. Return ordered collection
Notes:
- A rule is included if ANY of its mappings match the context
- Strict mappings require exact specimen match
- Non-strict mappings match any specimen for the role/target
5.1.1 Mapping Match Decision Logic
| Role Match | Target Match | Specimen Match | is_strict | Result |
|---|---|---|---|---|
| Yes | Yes | Yes | * | INCLUDE |
| Yes | Yes | No | true | EXCLUDE |
| Yes | Yes | No | false | INCLUDE |
| Yes | No | * | * | EXCLUDE |
| No | * | * | * | EXCLUDE |
Precedence: Role must match, then target, then specimen (if strict). Default: If no mappings exist for a rule, the rule is excluded.
5.2 Execution Order Algorithm (REQ-RULES-ENGINE-002)
Algorithm: Execute Rules in Precedence Order
Inputs:
- run: Run - Current run being analyzed
- analysableWells: WellCollection - Wells to analyze (excluding exported)
- runMixes: Collection - Mix configurations
- runTargets: Collection - Target configurations
- previousPatientWells: PatientWellCollection
- previousPecWells: PecWellCollection
- wells: WellCollection - All wells (for context)
Outputs:
- void (modifies wells in place)
Assumptions:
- Rules are pre-ordered by precedence from getRulesForPrecedenceOrder()
- Wells have been filtered to exclude exported wells
Steps:
1. Retrieve rules for precedence order based on well contexts
2. For each rule in order:
a. Update progress indicator
b. Filter wells based on is_allow_error_wells:
- IF is_allow_error_wells = false: reject wells with blocking errors
c. For each remaining well:
- For each observation in well:
- IF observation's role/target matches rule mapping:
- Execute rule.handle() with full context
3. Continue to next rule
Notes:
- Rules with lower precedence values execute first
- Duplicate precedence values execute in implementation-defined order
- Each rule sees the state modified by previous rules
5.2.1 Rule Execution Flowchart
5.3 Error Handling Flow (REQ-RULES-ENGINE-003)
Algorithm: Apply Error to Well and Check Prevention
Inputs:
- well: AnalyzableWell - Well being processed
- errorCode: string - Error code returned by rule
- config: ConfigurationRepository - For error code lookup
Outputs:
- void (modifies well state, may halt further processing)
Assumptions:
- Error codes are configured in error_codes table
- does_prevent_analyse determines blocking behavior
Steps:
1. Lookup error code configuration: errorConfig = config.getErrorByCode(errorCode)
2. IF errorConfig not found:
- Treat as blocking error
- Log warning
- Set well error and halt processing for this well
3. Apply error to well:
- Set well.error_code = errorCode
- Set well.lims_status from errorConfig
4. IF errorConfig.does_prevent_analyse = true:
- Mark well as having blocking error
- Future rules with is_allow_error_wells=false will skip this well
5. RETURN
Notes:
- Non-blocking errors allow subsequent rules to execute
- Blocking errors prevent subsequent rules (unless is_allow_error_wells=true)
- First error encountered is preserved; subsequent errors are logged but may not override
5.3.1 Error Flow Decision Logic
| does_prevent_analyse | is_allow_error_wells | Subsequent Rules Execute? |
|---|---|---|
| true | false | No - well skipped |
| true | true | Yes - rule explicitly allows |
| false | * | Yes - error is non-blocking |
Precedence: does_prevent_analyse is evaluated first; then is_allow_error_wells. Default: Unknown error codes are treated as blocking. Unreachable: None.
6. Error Handling
| Condition | Detection | Response | Fallback |
|---|---|---|---|
| Rule class not found | class_exists() returns false | Log error, skip rule | Analysis continues without rule |
| Error code not found | getErrorByCode() throws | Treat as blocking | Stop processing well |
| No rules mapped | Empty rules collection | Analysis completes | No rules executed |
| Invalid mapping | Query returns no matches | Skip rule for that context | Other contexts may execute |
7. Configuration
| Setting | Path | Default | Effect | REQ |
|---|---|---|---|---|
precedence | rules.precedence | - | Execution order (lower = earlier) | ENGINE-002 |
is_allow_error_wells | rules.is_allow_error_wells | false | Execute on wells with blocking errors | ENGINE-003 |
does_prevent_analyse | error_codes.does_prevent_analyse | varies | Error blocks further analysis | ENGINE-003 |
is_strict | rule_mappings.is_strict | false | Require exact specimen match | ENGINE-001 |
See Configuration Reference for full documentation.
8. Implementation Mapping
8.1 Code Locations
| Component | Path |
|---|---|
| Main Analyzer | app/Analyzer/Analyzer.php |
| Rule Model | app/Rule.php |
| Rule Mapping Model | app/RuleMapping.php |
| Error Code Model | app/ErrorCode.php |
| Rule Contract | app/Analyzer/Contracts/AnalyzerRuleContract.php |
| Configuration Repository | app/Analyzer/EloquentConfigurationRepository.php |
| Well Execution | app/Analyzer/Well.php |
| Rule Implementations | app/Analyzer/Rules/*.php (69 files) |
8.2 Requirement Traceability
| REQ ID | Design Section | Primary Code |
|---|---|---|
| REQ-RULES-ENGINE-001 | §5.1 | EloquentConfigurationRepository.getRulesForPrecedenceOrder(), RuleMapping.php |
| REQ-RULES-ENGINE-002 | §5.2 | Analyzer.executeRules(), Rule.execute() |
| REQ-RULES-ENGINE-003 | §5.3 | Well.hasErrorWhichPreventAnalyse(), ErrorCode.php |
9. Design Decisions
| Decision | Rationale | Alternatives Considered |
|---|---|---|
| Dynamic rule class resolution | Allows adding rules without code changes to framework | Static registry (rejected: requires code changes) |
| Precedence-based ordering | Deterministic, configurable order | Dependency-based (rejected: complex, fragile) |
| Error well filtering at execution | Rules can opt-in to error wells | Global filter (rejected: inflexible) |
| Mapping-based rule selection | Flexible per-context rules | All rules always run (rejected: wasteful, incorrect) |
| Single interface for all rules | Uniform contract simplifies execution | Per-type interfaces (rejected: complexity) |
10. Rule Catalog
10.1 Rules by Category
The following lists all 69 implemented rules organized by functional category:
Signal Quality Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
THRESHOLD | ThresholdRule | Validates runfile threshold values |
MANUAL_BASELINE | ManualBaselineRule | Detects manual baseline intervention |
MINIMUM_FLUORESCENCE | MinFluorescenceRule | Validates minimum fluorescence |
DOWNWARD_SIGMOID | SigmoidRule | Detects downward sigmoid patterns |
DOWNWARD_SIGMOID_CONTROL | SigmoidControlRule | Sigmoid checks for controls |
NEGATIVE_SIGMOID | NegativeSigmoidRule | Detects negative sigmoid patterns |
NEGATIVE_SIGMOID_CONTROL | NegativeSigmoidControlRule | Negative sigmoid for controls |
POSITIVE_SIGMOID | PositiveSigmoidRule | Detects positive sigmoid patterns |
POSITIVE_SIGMOID_CONTROL | PositiveSigmoidControlRule | Positive sigmoid for controls |
UNEXPECTED_FL | UnexpectedFlRule | Detects unexpected fluorescence |
Control Validation Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
BNC | BncRule | Validates negative controls |
BPEC | BpecRule | Validates BioProduct extraction controls |
BCC | BccRule | Validates control charts |
CONTROL_FAIL | ControlFailRule | Handles control failures |
MINCONTROLS | MinControlsRule | Validates minimum control counts |
MINEXTRACT | MinextractRule | Validates extraction controls |
Westgard Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
WG12S | Wg12SRule | Westgard 1:2s rule |
WG13S | Wg13SRule | Westgard 1:3s rule |
WG14S | Wg14SRule | Westgard 1:4s rule |
WG22S | Wg22SRule | Westgard 2:2s rule |
WG22S13S | Wg22S13SRule | Westgard 2:2s/1:3s combo |
WG7T | Wg7TRule | Westgard 7T rule |
WG7T13S | Wg7T13SRule | Westgard 7T/1:3s combo |
WGINERROR | WginerrorRule | Westgard in-error handling |
Inhibition Detection Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
INH | InhRule | Core inhibition detection |
ICCT | IcctRule | Internal control CT validation |
PICQUAL | PicqualRule | Qualitative inhibition detection |
PICQUAL_SERUM | PicqualSerumRule | Serum qualitative inhibition |
PICQUANT | PicquantRule | Quantitative inhibition detection |
PICQUANT_SERUM | PicquantSerumRule | Serum quantitative inhibition |
BIC | BicRule | Basic inhibition check |
BICQUAL | BicqualRule | Qualitative BIC |
SYSTEMIC_INHIBITION | SystemicInhibitionRule | Systemic inhibition patterns |
Classification and Discrepancy Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
WDCLS | WdclsRule | Well classification discrepancy |
WDCLSC | WdclscRule | Well classification discrepancy (controls) |
WDCLS_INVERT_SIGMOID_POS | WdclsInvertSigmoidPosRule | Classification with inverted sigmoid |
WDCLSC_INVERT_SIGMOID_POS | WdclscInvertSigmoidPosRule | Classification (controls) inverted sigmoid |
WDCT | WdctRule | Well CT discrepancy |
WDCTC | WdctcRule | Well CT discrepancy (controls) |
AMB | AmbRule | Ambiguous result handling |
ADJ | AdjRule | Adjudication rule |
ADJ_ZIKA | AdjZikaRule | Zika-specific adjudication |
INDETERMINATE_CTS | IndeterminateCtRule | Indeterminate CT handling |
INCONCLUSIVE | InconclusiveRule | Inconclusive result management |
Combined Outcome Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
COMBINED_OUTCOME | CombinedOutcomeRule | Core combined outcome matching |
SINGLE_WELL_COMBINED_OUTCOME | SingleWellCombinedOutcomeRule | Single-well outcome evaluation |
MULTIPLE_WELLS_COMBINED_OUTCOME | MultipleWellsCombinedOutcomeRule | Multi-well outcome evaluation |
COMBINED_OUTCOME_CONTROL | CombinedOutcomeControlRule | Combined outcomes for controls |
MIXES_MISSING | MissingMixesRule | Missing mix detection |
Quantification Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
RQUANT | RquantRule | Reportable quantity calculation |
QUANTITY_WEIGHT | QuantityWeightRule | Weighted quantity calculations |
QUANT_VALIDATION | QuantValidationRule | Quantity validation |
STDQT | StdqtRule | Standard quantity determination |
NOTTS_QUANT_MULTIPLY | NotesQuantMultiplyRule | Nottingham quantity multiplier |
QSSC | QsscRule | Quantification standard sample check |
LINEAR_REGRESSION_VALIDATION | LinearRegressionValidationRule | Linear regression validation |
Reporting Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
RQUAL | RqualRule | Qualitative reporting |
RRESOLUTION | RresolutionRule | Report resolution |
RWAC | RwacRule | Report with acceptance criteria |
RQUANT_AS_QUAL | RquantasqualRule | Report quantitative as qualitative |
WFINALCLS | WfinalclsRule | CT cutoff with final classification |
WREP | WrepRule | Repeat sample handling |
Other Rules
| Programmatic Name | Class | Purpose |
|---|---|---|
WT | WtRule | Well target rule |
WCAF | WcafRule | Well classification after final |
DELTA_CT | DeltaCtRule | Delta CT calculation |
SBCHECK | SbcheckRule | Sample barcode check |
RBACT | RbactRule | Reporting bacteria |
LIMS_EXPORT | LimsExportRule | LIMS export rule |
10.2 Typical Execution Order
While precedence is configurable per deployment, the typical execution order follows this pattern:
11. Performance Considerations
| Scenario | Concern | Mitigation |
|---|---|---|
| Large rule sets (>50 rules) | Query overhead | Rules cached in ConfigurationRepository |
| Large well counts (>384) | Iteration overhead | Filter before iterating; early exits |
| Complex mappings | Join complexity | Indexed queries; cached lookups |
| Westgard history | Historical queries | Batched queries; date-bounded |
12. Related Documents
| Document | Relevant Sections |
|---|---|
| SRS: rules.md | Requirements source |
| SDS: Combined Outcomes | Combined outcome rule design |
| SDS: Architecture | System architecture context |
| SDS: Configuration Reference | Rule configuration details |
| SDD: Algorithms | Legacy algorithm documentation |