Validation Engine
Status: Final Version: 1.0
Purpose
Define the comprehensive validation pipeline that executes schema, domain, referential, evidence, business rule, and anomaly checks against submissions.
Validation Types (Execution Order)
| Order | Type | Failure Impact | Examples |
|---|---|---|---|
| 1 | Schema | Hard fail (REJECTED) | Data type, required fields, format |
| 2 | Domain | Hard fail (REJECTED) | Min/max, regex, precision |
| 3 | Referential | Hard fail (REJECTED) | Cross-field consistency, sum checks |
| 4 | Evidence | Hard fail (REJECTED) | Required evidence attached |
| 5 | Business | Hard fail (REJECTED) | Custom logic, conditional rules |
| 6 | Anomaly | Warning only | Statistical outliers, YoY spikes |
Laravel Validation Service
class ValidationService
{
public function validate(MetricSubmission $submission): ValidationResult
{
$metric = $submission->metricDefinition;
$rules = $metric->validation_rules;
$errors = [];
$warnings = [];
// 1. Schema Validation
foreach ($rules->where('type', 'schema') as $rule) {
if (!$this->validateSchema($submission, $rule)) {
$errors[] = $rule->error_message;
}
}
// Stop if schema fails
if (!empty($errors)) {
return new ValidationResult(false, $errors, $warnings);
}
// 2-5. Other validations...
// 6. Anomaly Detection (warnings only)
foreach ($rules->where('type', 'anomaly') as $rule) {
if (!$this->checkAnomaly($submission, $rule)) {
$warnings[] = $rule->error_message;
}
}
return new ValidationResult(empty($errors), $errors, $warnings);
}
protected function validateSchema(MetricSubmission $submission, $rule)
{
$value = $submission->raw_data['value'];
return match($rule->rule) {
'required' => !is_null($value) && $value !== '',
'numeric' => is_numeric($value),
'integer' => is_int($value) || (is_numeric($value) && floor($value) == $value),
'boolean' => is_bool($value),
'date' => $this->isValidDate($value),
default => true,
};
}
protected function checkAnomaly(MetricSubmission $submission, $rule)
{
if ($rule->rule === 'z_score') {
$historicalValues = $this->getHistoricalValues($submission);
$zScore = $this->calculateZScore($submission->raw_data['value'], $historicalValues);
return abs($zScore) <= $rule->threshold;
}
if ($rule->rule === 'yoy_change') {
$lastYearValue = $this->getLastYearValue($submission);
if (!$lastYearValue) return true; // No baseline, skip check
$change = abs(($submission->raw_data['value'] - $lastYearValue) / $lastYearValue * 100);
return $change <= $rule->max_percentage;
}
return true;
}
}
Validation Result Storage
CREATE TABLE validation_results (
id BIGSERIAL PRIMARY KEY,
metric_submission_id BIGINT NOT NULL REFERENCES metric_submissions(id),
passed BOOLEAN NOT NULL,
errors JSONB, -- Array of error messages
warnings JSONB, -- Array of warning messages
validated_at TIMESTAMP DEFAULT NOW(),
INDEX (metric_submission_id)
);
Acceptance Criteria
- All 6 validation types implemented
- Schema validation stops pipeline if failed
- Anomaly checks produce warnings only (not hard failures)
- Validation results stored in dedicated table
- Collector receives field-level error details via API
Cross-References
- Metric Catalog - Validation rules
- Ingestion Pipeline
Change Log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-03 | Senior Product Architect | Initial validation engine specification |