Skip to main content
Version: Next

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

PropertyValue
IntentOrchestrate 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.
InputsWell observations, Rule Mappings (role/target/specimen combinations), Rule Setup (precedence), Error Codes configuration
OutputsAnalysis results with error/warning codes applied to wells
PrecedenceFramework 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 IDTitlePriorityComplexity
REQ-RULES-ENGINE-001Conditional Rule Execution Based on MappingMustMedium
REQ-RULES-ENGINE-002Sequential Rule Execution OrderMustMedium
REQ-RULES-ENGINE-003Error/Warning Flow ControlMustHigh

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

DirectionComponentPurpose
ConsumesWells & ObservationsPCR data to analyze
Rule MappingsWhich rules apply to which contexts
Rule SetupExecution order (precedence)
Error CodesError behavior configuration
ProducesAnalysis ResultsModified well data
Error/Warning CodesApplied to wells

2. Component Architecture

2.1 Component Diagram

2.2 Component Responsibilities

ComponentFileResponsibilityREQ Trace
Analyzerapp/Analyzer/Analyzer.phpMain orchestrator - initiates analysis, calls rule executionAll
EloquentConfigurationRepositoryapp/Analyzer/EloquentConfigurationRepository.phpRetrieves rules ordered by precedence, caches configurationENGINE-001, ENGINE-002
Rule (Model)app/Rule.phpRule configuration - precedence, type, programmatic name, error well flagENGINE-002
RuleMapping (Model)app/RuleMapping.phpMaps rules to role/target/specimen combinationsENGINE-001
AnalyzerRuleContractapp/Analyzer/Contracts/AnalyzerRuleContract.phpInterface contract all rules must implementAll
Wellapp/Analyzer/Well.phpExecutes individual rule on observation, handles error preventionENGINE-003
ErrorCode (Model)app/ErrorCode.phpDefines error behavior including "Does prevent Analyse"ENGINE-003

3. Data Design

3.1 Entities

EntityOwnerRead/WriteFields Used
rulesKITCFGReadid, title, programmatic_rule_name, type, precedence, is_allow_error_wells
rule_mappingsKITCFGReadrule_id, role_id, target_id, specimen_id, is_strict
error_codesKITCFGReaderror_code, does_prevent_analyse, lims_status
wellsRUNRPTRead/WriteAll observation fields, error_code, lims_status
observationsRUNRPTRead/WriteAll 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;
}
ParameterTypePurpose
$configConfigurationRepositoryAccess to all kit configuration
$observationAnalyzableObservationCurrent observation being analyzed
$runMixRunMixMix context for the well
$runTargetRunTargetTarget context for the observation
$wellAnalyzableWellWell containing the observation
$wellsWellCollectionAll wells in current run
$previousPatientWellsPatientWellCollectionHistorical wells for patient
$previousPecWellsPecWellCollectionHistorical PEC wells
$runTargetsRunTargetsCollectionAll 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_nameClass NameFile Path
WDCLSWdclsRuleAnalyzer/Rules/WdclsRule.php
COMBINED_OUTCOMECombinedOutcomeRuleAnalyzer/Rules/CombinedOutcomeRule.php
LINEAR_REGRESSION_VALIDATIONLinearRegressionValidationRuleAnalyzer/Rules/LinearRegressionValidationRule.php

4.3 Internal APIs

MethodClassPurpose
getRulesForPrecedenceOrder()ConfigurationRepositoryGet rules ordered by precedence for given context
execute()RuleExecute rule against all applicable wells
executeRule()WellExecute specific rule on well's observations
hasErrorWhichPreventAnalyse()WellCheck if well has blocking error
getErrorByCode()ConfigurationRepositoryRetrieve 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 MatchTarget MatchSpecimen Matchis_strictResult
YesYesYes*INCLUDE
YesYesNotrueEXCLUDE
YesYesNofalseINCLUDE
YesNo**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_analyseis_allow_error_wellsSubsequent Rules Execute?
truefalseNo - well skipped
truetrueYes - 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

ConditionDetectionResponseFallback
Rule class not foundclass_exists() returns falseLog error, skip ruleAnalysis continues without rule
Error code not foundgetErrorByCode() throwsTreat as blockingStop processing well
No rules mappedEmpty rules collectionAnalysis completesNo rules executed
Invalid mappingQuery returns no matchesSkip rule for that contextOther contexts may execute

7. Configuration

SettingPathDefaultEffectREQ
precedencerules.precedence-Execution order (lower = earlier)ENGINE-002
is_allow_error_wellsrules.is_allow_error_wellsfalseExecute on wells with blocking errorsENGINE-003
does_prevent_analyseerror_codes.does_prevent_analysevariesError blocks further analysisENGINE-003
is_strictrule_mappings.is_strictfalseRequire exact specimen matchENGINE-001

See Configuration Reference for full documentation.


8. Implementation Mapping

8.1 Code Locations

ComponentPath
Main Analyzerapp/Analyzer/Analyzer.php
Rule Modelapp/Rule.php
Rule Mapping Modelapp/RuleMapping.php
Error Code Modelapp/ErrorCode.php
Rule Contractapp/Analyzer/Contracts/AnalyzerRuleContract.php
Configuration Repositoryapp/Analyzer/EloquentConfigurationRepository.php
Well Executionapp/Analyzer/Well.php
Rule Implementationsapp/Analyzer/Rules/*.php (69 files)

8.2 Requirement Traceability

REQ IDDesign SectionPrimary Code
REQ-RULES-ENGINE-001§5.1EloquentConfigurationRepository.getRulesForPrecedenceOrder(), RuleMapping.php
REQ-RULES-ENGINE-002§5.2Analyzer.executeRules(), Rule.execute()
REQ-RULES-ENGINE-003§5.3Well.hasErrorWhichPreventAnalyse(), ErrorCode.php

9. Design Decisions

DecisionRationaleAlternatives Considered
Dynamic rule class resolutionAllows adding rules without code changes to frameworkStatic registry (rejected: requires code changes)
Precedence-based orderingDeterministic, configurable orderDependency-based (rejected: complex, fragile)
Error well filtering at executionRules can opt-in to error wellsGlobal filter (rejected: inflexible)
Mapping-based rule selectionFlexible per-context rulesAll rules always run (rejected: wasteful, incorrect)
Single interface for all rulesUniform contract simplifies executionPer-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 NameClassPurpose
THRESHOLDThresholdRuleValidates runfile threshold values
MANUAL_BASELINEManualBaselineRuleDetects manual baseline intervention
MINIMUM_FLUORESCENCEMinFluorescenceRuleValidates minimum fluorescence
DOWNWARD_SIGMOIDSigmoidRuleDetects downward sigmoid patterns
DOWNWARD_SIGMOID_CONTROLSigmoidControlRuleSigmoid checks for controls
NEGATIVE_SIGMOIDNegativeSigmoidRuleDetects negative sigmoid patterns
NEGATIVE_SIGMOID_CONTROLNegativeSigmoidControlRuleNegative sigmoid for controls
POSITIVE_SIGMOIDPositiveSigmoidRuleDetects positive sigmoid patterns
POSITIVE_SIGMOID_CONTROLPositiveSigmoidControlRulePositive sigmoid for controls
UNEXPECTED_FLUnexpectedFlRuleDetects unexpected fluorescence

Control Validation Rules

Programmatic NameClassPurpose
BNCBncRuleValidates negative controls
BPECBpecRuleValidates BioProduct extraction controls
BCCBccRuleValidates control charts
CONTROL_FAILControlFailRuleHandles control failures
MINCONTROLSMinControlsRuleValidates minimum control counts
MINEXTRACTMinextractRuleValidates extraction controls

Westgard Rules

Programmatic NameClassPurpose
WG12SWg12SRuleWestgard 1:2s rule
WG13SWg13SRuleWestgard 1:3s rule
WG14SWg14SRuleWestgard 1:4s rule
WG22SWg22SRuleWestgard 2:2s rule
WG22S13SWg22S13SRuleWestgard 2:2s/1:3s combo
WG7TWg7TRuleWestgard 7T rule
WG7T13SWg7T13SRuleWestgard 7T/1:3s combo
WGINERRORWginerrorRuleWestgard in-error handling

Inhibition Detection Rules

Programmatic NameClassPurpose
INHInhRuleCore inhibition detection
ICCTIcctRuleInternal control CT validation
PICQUALPicqualRuleQualitative inhibition detection
PICQUAL_SERUMPicqualSerumRuleSerum qualitative inhibition
PICQUANTPicquantRuleQuantitative inhibition detection
PICQUANT_SERUMPicquantSerumRuleSerum quantitative inhibition
BICBicRuleBasic inhibition check
BICQUALBicqualRuleQualitative BIC
SYSTEMIC_INHIBITIONSystemicInhibitionRuleSystemic inhibition patterns

Classification and Discrepancy Rules

Programmatic NameClassPurpose
WDCLSWdclsRuleWell classification discrepancy
WDCLSCWdclscRuleWell classification discrepancy (controls)
WDCLS_INVERT_SIGMOID_POSWdclsInvertSigmoidPosRuleClassification with inverted sigmoid
WDCLSC_INVERT_SIGMOID_POSWdclscInvertSigmoidPosRuleClassification (controls) inverted sigmoid
WDCTWdctRuleWell CT discrepancy
WDCTCWdctcRuleWell CT discrepancy (controls)
AMBAmbRuleAmbiguous result handling
ADJAdjRuleAdjudication rule
ADJ_ZIKAAdjZikaRuleZika-specific adjudication
INDETERMINATE_CTSIndeterminateCtRuleIndeterminate CT handling
INCONCLUSIVEInconclusiveRuleInconclusive result management

Combined Outcome Rules

Programmatic NameClassPurpose
COMBINED_OUTCOMECombinedOutcomeRuleCore combined outcome matching
SINGLE_WELL_COMBINED_OUTCOMESingleWellCombinedOutcomeRuleSingle-well outcome evaluation
MULTIPLE_WELLS_COMBINED_OUTCOMEMultipleWellsCombinedOutcomeRuleMulti-well outcome evaluation
COMBINED_OUTCOME_CONTROLCombinedOutcomeControlRuleCombined outcomes for controls
MIXES_MISSINGMissingMixesRuleMissing mix detection

Quantification Rules

Programmatic NameClassPurpose
RQUANTRquantRuleReportable quantity calculation
QUANTITY_WEIGHTQuantityWeightRuleWeighted quantity calculations
QUANT_VALIDATIONQuantValidationRuleQuantity validation
STDQTStdqtRuleStandard quantity determination
NOTTS_QUANT_MULTIPLYNotesQuantMultiplyRuleNottingham quantity multiplier
QSSCQsscRuleQuantification standard sample check
LINEAR_REGRESSION_VALIDATIONLinearRegressionValidationRuleLinear regression validation

Reporting Rules

Programmatic NameClassPurpose
RQUALRqualRuleQualitative reporting
RRESOLUTIONRresolutionRuleReport resolution
RWACRwacRuleReport with acceptance criteria
RQUANT_AS_QUALRquantasqualRuleReport quantitative as qualitative
WFINALCLSWfinalclsRuleCT cutoff with final classification
WREPWrepRuleRepeat sample handling

Other Rules

Programmatic NameClassPurpose
WTWtRuleWell target rule
WCAFWcafRuleWell classification after final
DELTA_CTDeltaCtRuleDelta CT calculation
SBCHECKSbcheckRuleSample barcode check
RBACTRbactRuleReporting bacteria
LIMS_EXPORTLimsExportRuleLIMS export rule

10.2 Typical Execution Order

While precedence is configurable per deployment, the typical execution order follows this pattern:


11. Performance Considerations

ScenarioConcernMitigation
Large rule sets (>50 rules)Query overheadRules cached in ConfigurationRepository
Large well counts (>384)Iteration overheadFilter before iterating; early exits
Complex mappingsJoin complexityIndexed queries; cached lookups
Westgard historyHistorical queriesBatched queries; date-bounded

DocumentRelevant Sections
SRS: rules.mdRequirements source
SDS: Combined OutcomesCombined outcome rule design
SDS: ArchitectureSystem architecture context
SDS: Configuration ReferenceRule configuration details
SDD: AlgorithmsLegacy algorithm documentation