Skip to content

Manual Bounds Detector

The Manual Bounds detector is a simple threshold-based method that uses user-specified bounds for anomaly detection. Ideal when domain knowledge exists about acceptable ranges.

Manual Bounds is particularly effective for:

  • Known thresholds - Clear business rules or SLA requirements
  • Hard limits - Physical or logical constraints (e.g., percentages 0-100%)
  • Binary alerts - Simple “too high” or “too low” notifications
  • Real-time detection - No historical data needed
  • Compliance monitoring - Regulatory or contractual limits

The Manual Bounds detector works by:

  1. Define bounds - User specifies lower_bound and/or upper_bound
  2. Compare values - Check if current value is outside bounds
  3. Detect anomalies - Any value outside bounds is anomalous

No historical data required - purely threshold-based comparison.

lower_bound (float or None, default: None)

Section titled “lower_bound (float or None, default: None)”

Minimum acceptable value. Values below this are considered anomalous.

  • None = no lower limit
  • Must be less than upper_bound if both specified
  • At least one of lower_bound or upper_bound must be specified

Example:

detectors:
- type: manual_bounds
params:
lower_bound: 0.0 # Values below 0 are anomalies

upper_bound (float or None, default: None)

Section titled “upper_bound (float or None, default: None)”

Maximum acceptable value. Values above this are considered anomalous.

  • None = no upper limit
  • Must be greater than lower_bound if both specified
  • At least one of lower_bound or upper_bound must be specified

Example:

detectors:
- type: manual_bounds
params:
upper_bound: 100.0 # Values above 100 are anomalies

Input transformation applied before the bounds comparison. Bounds are checked against the transformed value, not the raw metric.

  • values - raw value (no transformation)
  • changes - relative change (v[t] - v[t-1]) / v[t-1]
  • absolute_changes - absolute change v[t] - v[t-1]
  • log_changes - log change log(v[t]) - log(v[t-1])

For the change-based types the first point has no predecessor, so it is NaN and skipped (no anomaly, no alert). input_type is part of the detector_id hash — changing it creates a new detector and recomputes detections.

Example:

# Alert when the value jumps more than 20% above the previous point
detectors:
- type: manual_bounds
params:
input_type: changes
upper_bound: 0.2

Manual Bounds detector does not use:

  • window_size - No historical window needed
  • min_samples - No warm-up period needed
  • threshold - Bounds are explicit
  • seasonality_components - No seasonality support

start_time and batch_size are execution-level parameters (placed under params:) common to all detectors — they control where detection starts and how much data is processed per batch, not the algorithm itself:

  • start_time - Start detecting from this timestamp (optional; defaults to the metric’s loading_start_time, so the first run detects across all loaded history)
  • batch_size - Process data in batches (optional)

Alert when values exceed maximum:

name: cpu_usage
interval: 1min
query: "SELECT timestamp, cpu_percent FROM system_metrics"
detectors:
- type: manual_bounds
params:
upper_bound: 90.0 # Alert when CPU > 90%

Alert when values drop below minimum:

name: cache_hit_rate
interval: 5min
query: "SELECT timestamp, hit_rate FROM cache_stats"
detectors:
- type: manual_bounds
params:
lower_bound: 0.8 # Alert when hit rate < 80%

Alert when values are outside acceptable range:

name: queue_size
interval: 1min
query: "SELECT timestamp, queue_length FROM processing_queue"
detectors:
- type: manual_bounds
params:
lower_bound: 0 # Alert if queue is negative (impossible)
upper_bound: 10000 # Alert if queue exceeds capacity

Monitor service level agreement:

name: api_response_time
interval: 1min
query: "SELECT timestamp, p95_response_ms FROM api_logs"
detectors:
- type: manual_bounds
params:
upper_bound: 1000 # SLA: 95th percentile < 1000ms

Monitor metrics with natural 0-100% range:

name: disk_usage
interval: 5min
query: "SELECT timestamp, disk_used_pct FROM storage_metrics"
detectors:
- type: manual_bounds
params:
upper_bound: 85.0 # Alert when disk > 85% full

Zero-tolerance error monitoring:

name: critical_errors
interval: 1min
query: "SELECT timestamp, error_count FROM application_logs"
detectors:
- type: manual_bounds
params:
upper_bound: 0 # Any error is anomalous

Physical system with operating range:

name: server_temperature
interval: 30s
query: "SELECT timestamp, temp_celsius FROM hardware_sensors"
detectors:
- type: manual_bounds
params:
lower_bound: 10.0 # Too cold (cooling failure)
upper_bound: 75.0 # Too hot (overheating)
  • Known thresholds - Clear business rules or requirements
  • SLA monitoring - Contractual limits (response time, uptime)
  • Hard constraints - Physical/logical limits (0-100%, positive values)
  • Compliance - Regulatory requirements
  • Simple alerting - Binary “too high/low” notifications
  • Real-time detection - No historical warm-up period needed
  • No clear threshold → Statistical detectors (MAD, Z-Score, IQR)
  • Dynamic patterns → MAD with seasonality
  • Relative anomalies → Z-Score or MAD (detect deviations from normal)
  • Exploratory analysis → Start with MAD to discover natural thresholds
  • Instant detection - No warm-up period, works from first point
  • Simple - Easy to understand and explain to stakeholders
  • Predictable - No statistical variability
  • Fast - Fastest detector (simple comparison)
  • Transparent - Clear why something is anomalous
  • Domain knowledge - Leverages expert knowledge of acceptable ranges
  • Manual tuning - Requires domain knowledge to set bounds
  • No adaptation - Doesn’t learn from data patterns
  • Static - Can’t handle seasonality or trends
  • False positives - May alert on valid but unusual spikes
  • Maintenance - Bounds may need updating as system changes
  • Speed: O(1) work per point — the lightest detector (a single bounds comparison)
  • Memory: O(1) - No historical data stored
  • CPU: Minimal (simple comparison)
  • Fastest detector - No statistical calculations

Each detection result includes metadata:

{
# Only when input_type != "values":
"preprocessing": {"input_type": "changes"},
# Only for anomalies:
"direction": "above", # "above" or "below"
"distance": 15.32, # Absolute distance from bound
"severity": 0.152 # Relative severity
}

Severity represents relative distance from bound:

With both bounds:

bound_range = upper_bound - lower_bound
severity = distance / bound_range

With only one bound:

severity = distance # Absolute distance

Interpretation:

  • severity = 0.1 → 10% of range outside bounds
  • severity = 0.5 → 50% of range outside bounds
  • severity = 1.0 → Full range width outside bounds
  • severity > 1.0 → More than full range outside

Example:

lower_bound: 10
upper_bound: 90
value: 100
distance = 100 - 90 = 10
bound_range = 90 - 10 = 80
severity = 10 / 80 = 0.125
  • NaN values are skipped
  • Marked as is_anomaly=False with "reason": "missing_data"
  • No alert triggered
lower_bound: 50.0
upper_bound: 50.0 # ERROR: Invalid configuration

Validation will fail: lower_bound must be less than upper_bound

params: {} # ERROR: Invalid configuration

Validation will fail: At least one of lower_bound or upper_bound must be specified

Not supported - use null (None) instead:

# WRONG:
lower_bound: -inf
upper_bound: inf
# CORRECT (no lower limit, only an upper bound):
lower_bound: null # No lower limit
upper_bound: 100.0 # Alert when value exceeds 100

Setting both to null is invalid — at least one bound is required (validation fails with At least one of lower_bound or upper_bound must be specified).

FeatureManual BoundsMADZ-ScoreIQR
Historical dataNot neededRequiredRequiredRequired
Warm-up periodNonemin_samplesmin_samplesmin_samples
AdaptivityStaticAdaptsAdaptsAdapts
SeasonalityNoExcellentYesYes
Domain knowledgeRequiredNot neededNot neededNot needed
Setup effortManual tuningAutoAutoAuto
PerformanceFastestFastFastFast
TransparencyVery clearStatisticalStatisticalStatistical
# API response time must be < 500ms (P95)
name: api_latency_p95
detectors:
- type: manual_bounds
params:
upper_bound: 500.0
# Memory usage should not exceed 8GB
name: memory_usage_gb
detectors:
- type: manual_bounds
params:
upper_bound: 8.0
# Daily revenue should be > $10,000
name: daily_revenue
detectors:
- type: manual_bounds
params:
lower_bound: 10000.0
# Uptime must be > 99.9%
name: service_uptime_pct
detectors:
- type: manual_bounds
params:
lower_bound: 99.9
# Temperature sensor range: 0-100°C
name: sensor_temperature
detectors:
- type: manual_bounds
params:
lower_bound: 0.0
upper_bound: 100.0

Good:

# Clear business rule
upper_bound: 100 # Max 100 concurrent users

Avoid:

# Arbitrary threshold without justification
upper_bound: 42.7 # Why 42.7?

Use Manual Bounds for hard limits + statistical detector for unusual patterns:

detectors:
# Hard limit: CPU should never exceed 95%
- type: manual_bounds
params:
upper_bound: 95.0
# Soft limit: detect unusual CPU patterns
- type: mad
params:
threshold: 3.0
window_size: 288

Add comments explaining why bounds were chosen:

detectors:
- type: manual_bounds
params:
upper_bound: 1000 # SLA requirement: P95 < 1000ms
  • System capacity changes → update bounds
  • Business requirements change → update bounds
  • False positive/negative rate too high → reconsider bounds

When starting monitoring:

  1. Use Manual Bounds for known hard limits
  2. Use MAD/Z-Score to discover natural patterns
  3. Convert learned patterns to Manual Bounds if stable