LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 158 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/23
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BMixed Precision TrainingModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Production Agent
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight InterpolationPrompt Optimization with DSPyRecursive Language Models (RLM)
🤖Advanced Agents & Retrieval0/14
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryMulti-Agent Orchestration
⚡Inference & Production Scale0/20
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementContext EngineeringMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
Back to Topics
LearnProduction ML SystemsForecasting and Anomaly Detection
📊MediumEvaluation & Benchmarks

Forecasting and Anomaly Detection

Forecast batch-job demand with time-aware evaluation and turn large forecast errors into reviewable operational alerts.

16 min read
Learning path
Step 46 of 158 in the full curriculum
Ranking and Recommendation SystemsMonitoring Predictive Models

Ranking and recommendation chose an ordered slate at one moment. Forecasting asks the next production question: how many batch jobs will run tomorrow, and which observed counts fall outside that expectation?

Forecasting predicts a value for a later time. Anomaly detection asks whether an observation is unusual enough to inspect. The two jobs fit together: first estimate expected batch-job volume, then compare the actual count with that expectation. Monitoring chapters later reuse the same residual signal as a live operational check.

Seven-day batch job forecast chart comparing a weekly-lag baseline and its plus-or-minus three batch job band with observed volumes, highlighting Friday actual volume 160 against forecast 132 as a plus-28 review event, alongside holdout error and interval coverage evidence. Seven-day batch job forecast chart comparing a weekly-lag baseline and its plus-or-minus three batch job band with observed volumes, highlighting Friday actual volume 160 against forecast 132 as a plus-28 review event, alongside holdout error and interval coverage evidence.
The weekly-lag baseline keeps normal-day MAE at 1.5 batch jobs. Friday's observed 160 falls well outside the 129..135 interval around forecast 132, creating a +28 launch review rather than an automatic incident.

Start with an ordered batch-job series

A time series is a sequence of observations indexed by time. This lesson uses daily outbound batch-job counts for one model-serving cluster. Each row has a date, a batch-job count, and a flag for a planned model launch.

The forecast horizon is how far ahead you predict. A one-day horizon answers tomorrow's staffing question. A seven-day horizon helps reserve capacity for the coming week. This lab predicts one day at a time, then evaluates a full future week.

Build four weeks of daily counts. The weekday pattern repeats, but small changes keep the series realistic. The final Friday launch event creates a larger jump.

build-batch-job-series.py
1from datetime import date, timedelta 2from hashlib import sha256 3from json import dumps 4from math import ceil 5 6start = date(2026, 1, 5) 7weekly_pattern = [100, 112, 115, 118, 132, 82, 76] 8weekly_noise = [ 9 [0, 0, 0, 0, 0, 0, 0], 10 [2, -2, 3, -2, 2, 2, 2], 11 [4, -2, 4, -2, 0, 1, 2], 12 [3, 1, 2, -1, 28, 2, 1], 13] 14 15rows = [] 16for week, noise in enumerate(weekly_noise): 17 for weekday, (baseline, offset) in enumerate(zip(weekly_pattern, noise)): 18 rows.append( 19 { 20 "date": start + timedelta(days=7 * week + weekday), 21 "weekday": weekday, 22 "volume": baseline + offset, 23 "launch_event": week == 3 and weekday == 4, 24 } 25 ) 26 27print("rows:", len(rows)) 28print("first date:", rows[0]["date"]) 29print("last date:", rows[-1]["date"]) 30print("launch_event day:", next(row["date"] for row in rows if row["launch_event"]))
Output
1rows: 28 2first date: 2026-01-05 3last date: 2026-02-01 4launch_event day: 2026-01-30

The fixture is deliberately small enough to inspect. In production, each model-serving cluster, service tier, and forecast horizon may become a separate series or slice.

Keep future rows out of training

Ordinary tabular prediction often starts with a random train/test split. That's unsafe for forecasting. A shuffled training set can contain January 31 while its test set contains January 6. A model evaluated that way has learned from the future relative to some test rows.

Compare an invalid shuffled split with a chronological split. The audit prints whether the latest training date reaches or passes the earliest test date.

audit-time-splits.py
1shuffled_train = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26] 2shuffled_test = [index for index in range(len(rows)) if index not in shuffled_train] 3time_train = list(range(21)) 4time_test = list(range(21, 28)) 5 6def split_summary(name, train_indices, test_indices): 7 latest_train = max(rows[index]["date"] for index in train_indices) 8 earliest_test = min(rows[index]["date"] for index in test_indices) 9 future_leak = latest_train >= earliest_test 10 print( 11 f"{name}: latest_train={latest_train} " 12 f"earliest_test={earliest_test} future_leak={future_leak}" 13 ) 14 15split_summary("shuffled", shuffled_train, shuffled_test) 16split_summary("time-aware", time_train, time_test) 17assert max(time_train) < min(time_test)
Output
1shuffled: latest_train=2026-01-31 earliest_test=2026-01-06 future_leak=True 2time-aware: latest_train=2026-01-25 earliest_test=2026-01-26 future_leak=False

Hyndman and Athanasopoulos describe time-series cross-validation as repeated evaluation where each test observation occurs after the observations used for training.[1]Reference 1Forecasting: Principles and Practice, Third Edition.https://otexts.com/fpp3/ You can expand the training history or keep a rolling window. Either way, the time direction stays intact.

Establish a same-weekday baseline

Batch-job volume has seasonality when a pattern repeats at a known calendar frequency. Saturday volume may stay lower than weekday volume. Predicting tomorrow from yesterday alone will overreact around each weekend.

A seasonal naive forecast reuses the observation from the matching previous season. For daily batch job data with a weekly pattern, forecast Monday from last Monday, Tuesday from last Tuesday, and so on.[1]Reference 1Forecasting: Principles and Practice, Third Edition.https://otexts.com/fpp3/

Target daySame day last weekForecastActualForecast error
Monday, Jan 26Monday, Jan 19104103-1
Friday, Jan 30Friday, Jan 23132160+28

The Friday arithmetic is 160 - 132 = 28. Positive error means the observed count exceeded the forecast. Negative error means the forecast was too high.

Compute the complete holdout week. Each forecast reads the value exactly seven rows earlier.

forecast-with-weekly-lag.py
1holdout = list(range(21, 28)) 2 3def weekly_lag(index): 4 return rows[index - 7]["volume"] 5 6for index in holdout: 7 forecast = weekly_lag(index) 8 actual = rows[index]["volume"] 9 error = actual - forecast 10 print( 11 f"{rows[index]['date']} forecast={forecast} " 12 f"actual={actual} error={error:+d}" 13 )
Output
12026-01-26 forecast=104 actual=103 error=-1 22026-01-27 forecast=110 actual=113 error=+3 32026-01-28 forecast=119 actual=117 error=-2 42026-01-29 forecast=116 actual=117 error=+1 52026-01-30 forecast=132 actual=160 error=+28 62026-01-31 forecast=83 actual=84 error=+1 72026-02-01 forecast=78 actual=77 error=-1

Simple baselines matter. A complex model should beat the seasonal naive forecast on future windows before its extra features and operational cost are justified.

Measure forecast error in batch jobs

Mean absolute error (MAE) averages the size of each mistake without letting positive and negative errors cancel:

MAE=1n∑t=1n∣yt−y^t∣\text{MAE} = \frac{1}{n}\sum_{t=1}^{n}|y_t - \hat{y}_t|MAE=n1​t=1∑n​∣yt​−y^​t​∣

Here nnn is the number of evaluated days, yty_tyt​ is observed volume on day ttt, and y^t\hat{y}_ty^​t​ is the forecast for that day. For the holdout week, the absolute errors are 1, 3, 2, 1, 28, 1, 1. Their sum is 37, so MAE is 37 / 7 = 5.3 batch jobs.

Calculate overall MAE and normal-day MAE separately. The second metric excludes the known launch event day so you can see baseline behavior without hiding the operational spike.

measure-holdout-mae.py
1def mean_absolute_error(errors): 2 return sum(abs(error) for error in errors) / len(errors) 3 4holdout_errors = [ 5 rows[index]["volume"] - weekly_lag(index) 6 for index in holdout 7] 8normal_errors = [ 9 rows[index]["volume"] - weekly_lag(index) 10 for index in holdout 11 if not rows[index]["launch_event"] 12] 13 14print("holdout errors:", holdout_errors) 15print("holdout MAE:", round(mean_absolute_error(holdout_errors), 1)) 16print("normal-day MAE:", round(mean_absolute_error(normal_errors), 1))
Output
1holdout errors: [-1, 3, -2, 1, 28, 1, -1] 2holdout MAE: 5.3 3normal-day MAE: 1.5

MAE remains in batch jobs, which makes it understandable to operations. It doesn't tell you everything: underforecasting a capacity spike may cost more than overforecasting by the same amount. Record asymmetric business cost when the decision requires it.

Backtest more than one future window

A single future week is fragile evidence. Rolling-origin evaluation moves the forecast origin forward and measures several later windows. In this fixture, the first fold evaluates January 19 through January 25. The second evaluates January 26 through February 1.

Run both folds. The seasonal baseline can forecast each day because each fold starts after at least one full week of history.

run-rolling-origin-backtest.py
1folds = [] 2for origin in (14, 21): 3 indices = list(range(origin, origin + 7)) 4 errors = [ 5 rows[index]["volume"] - weekly_lag(index) 6 for index in indices 7 ] 8 fold = { 9 "start": rows[indices[0]]["date"], 10 "end": rows[indices[-1]]["date"], 11 "mae": round(mean_absolute_error(errors), 1), 12 } 13 folds.append(fold) 14 print(f"{fold['start']} to {fold['end']}: MAE={fold['mae']}")
Output
12026-01-19 to 2026-01-25: MAE=0.9 22026-01-26 to 2026-02-01: MAE=5.3

Backtest by model-serving cluster, service tier, weekday, and launch-event status. A global average can hide one cluster that routinely underforecasts peak demand. The target must also match the decision: submitted job count helps GPU reservation, failed job count helps incident response, and queue-delay count helps operator notification.

Distinguish forecast errors from residuals

Two related quantities often get called residuals, but the distinction is useful:

QuantityWhere it comes fromFormulaWhat it tells you
residualtraining data after fitting a modelobserved value minus fitted valuewhether model fits known history
forecast errorlater observation after making a forecastobserved value minus forecastwhether model predicted unseen future data

Hyndman and Athanasopoulos make this distinction explicitly.[1]Reference 1Forecasting: Principles and Practice, Third Edition.https://otexts.com/fpp3/ Our Friday +28 is a forecast error because Friday's observed volume wasn't available when the forecast was issued. An operations team may casually say "residual alert," but the stored artifact should name the measured quantity precisely.

Diagram showing Daily batch-job counts ordered history, Seasonal baseline same weekday, Future forecast expected range, and Observed volume. Diagram showing Daily batch-job counts ordered history, Seasonal baseline same weekday, Future forecast expected range, and Observed volume.
Daily batch-job counts ordered history, Seasonal baseline same weekday, Future forecast expected range, and Observed volume.

Calibrate an error band from earlier days

A point forecast such as 132 batch jobs doesn't express uncertainty. A prediction interval pairs a forecast with a lower and upper bound intended to cover a stated proportion of future outcomes. Forecast intervals should be evaluated for coverage, not displayed beside the point estimate alone.[1]Reference 1Forecasting: Principles and Practice, Third Edition.https://otexts.com/fpp3/

For a small first-principles exercise, use the 95th percentile of earlier absolute one-day forecast errors. This produces a symmetric empirical error band. The code deliberately calibrates on January 12 through January 25, before the January 26 holdout starts, then measures coverage again on the later holdout week.

calibrate-empirical-error-band.py
1calibration = list(range(7, 21)) 2calibration_errors = [ 3 rows[index]["volume"] - weekly_lag(index) 4 for index in calibration 5] 6target_coverage = 0.95 7interval_policy = "empirical-absolute-error-p95-v1" 8 9def nearest_rank_quantile(values, probability): 10 ordered = sorted(values) 11 rank = max(0, ceil(probability * len(ordered)) - 1) 12 return ordered[rank] 13 14error_band = nearest_rank_quantile( 15 [abs(error) for error in calibration_errors], 16 probability=target_coverage, 17) 18calibration_coverage = sum( 19 abs(error) <= error_band 20 for error in calibration_errors 21) / len(calibration_errors) 22holdout_coverage = sum( 23 abs(error) <= error_band 24 for error in holdout_errors 25) / len(holdout_errors) 26 27assert max(calibration) < min(holdout) 28print("calibration errors:", calibration_errors) 29print("95% empirical absolute-error band:", error_band) 30print("in-sample calibration coverage:", round(calibration_coverage, 3)) 31print("later holdout coverage:", round(holdout_coverage, 3))
Output
1calibration errors: [2, -2, 3, -2, 2, 2, 2, 2, 0, 1, 0, -2, -1, 0] 295% empirical absolute-error band: 3 3in-sample calibration coverage: 1.0 4later holdout coverage: 0.857

The ±3 band explains the mechanics, not a production guarantee. Fourteen calibration errors are too few for a stable 95% interval. Its in-sample calibration coverage is 1.000, while later holdout coverage is 6 / 7 = 0.857 because Friday falls outside the band. A production system should measure out-of-sample interval coverage across rolling windows, forecast horizons, model-serving clusters, and important demand slices.

Turn an unusual error into a review artifact

An anomaly is an observation unusual enough to inspect. It isn't proof that a cluster, pipeline, or model failed. Friday's jump could come from a planned model launch, duplicated event ingestion, a large backfill, or genuine demand growth.

Apply the empirical band to the held-out week. The alert stores enough context to reproduce the comparison.

create-capacity-alert.py
1forecast_version = "weekly-lag-v1" 2training_cutoff = str(rows[20]["date"]) 3owner = "capacity-ops" 4 5alerts = [] 6for index in holdout: 7 forecast = weekly_lag(index) 8 actual = rows[index]["volume"] 9 forecast_error = actual - forecast 10 if abs(forecast_error) > error_band: 11 alerts.append( 12 { 13 "series": "fc-a.batch_jobs", 14 "forecast_version": forecast_version, 15 "training_cutoff": training_cutoff, 16 "interval_policy": interval_policy, 17 "date": str(rows[index]["date"]), 18 "forecast": forecast, 19 "actual": actual, 20 "forecast_error": forecast_error, 21 "lower": forecast - error_band, 22 "upper": forecast + error_band, 23 "launch_event": rows[index]["launch_event"], 24 "action": "review", 25 "owner": owner, 26 "resolution": "pending", 27 } 28 ) 29 30for alert in alerts: 31 print(alert)
Output
1{'series': 'fc-a.batch_jobs', 'forecast_version': 'weekly-lag-v1', 'training_cutoff': '2026-01-25', 'interval_policy': 'empirical-absolute-error-p95-v1', 'date': '2026-01-30', 'forecast': 132, 'actual': 160, 'forecast_error': 28, 'lower': 129, 'upper': 135, 'launch_event': True, 'action': 'review', 'owner': 'capacity-ops', 'resolution': 'pending'}

The baseline caught Friday because 160 falls outside [129, 135]. The launch-event flag doesn't erase the event. It changes the first investigation step.

Route alerts with context. A planned model launch still deserves capacity review, but it shouldn't automatically create an incident page.

route-alert-with-context.py
1def route_alert(alert): 2 if alert["launch_event"]: 3 return "review planned model launch capacity" 4 return "page unexpected batch-job spike" 5 6for alert in alerts: 7 print(alert["date"], "->", route_alert(alert))
Output
12026-01-30 -> review planned model launch capacity

Log the forecast version, training cutoff, interval policy version, observed value, error, known-event flags, owner, and eventual resolution. That receipt helps operations act now and helps future model reviews learn from the alert.

Evaluate alert usefulness separately

Low MAE doesn't guarantee useful alerts. An alert threshold can still page too often or miss disruptions. Once reviewers label whether each historical alert was actionable, measure alert precision and recall:

MetricQuestion
alert precisionOf the alerts sent to review, how many were actionable?
alert recallOf the actionable disruptions, how many did the policy surface?

Compute both metrics on a five-event resolution fixture. The policy surfaced two useful events, sent one noisy alert, and missed one disruption.

evaluate-alert-policy.py
1resolved_events = [ 2 {"alert": True, "actionable": True}, 3 {"alert": True, "actionable": False}, 4 {"alert": False, "actionable": True}, 5 {"alert": False, "actionable": False}, 6 {"alert": True, "actionable": True}, 7] 8 9true_positives = sum(event["alert"] and event["actionable"] for event in resolved_events) 10false_positives = sum(event["alert"] and not event["actionable"] for event in resolved_events) 11false_negatives = sum(not event["alert"] and event["actionable"] for event in resolved_events) 12 13def rate_or_none(numerator, denominator): 14 return round(numerator / denominator, 3) if denominator else None 15 16alert_precision = rate_or_none(true_positives, true_positives + false_positives) 17alert_recall = rate_or_none(true_positives, true_positives + false_negatives) 18print("alert precision:", alert_precision) 19print("alert recall:", alert_recall)
Output
1alert precision: 0.667 2alert recall: 0.667

Tune the policy against operational cost. A missed cluster disruption and a noisy review ticket don't have equal consequences. If a denominator is zero, report None: no evidence isn't the same as measured failure. Page only when urgency warrants interruption; otherwise queue a review with the same evidence.

Reproduce a data-quality anomaly

Not every spike represents demand. A duplicated telemetry event can inflate a count before forecasting code sees it. Reproduce that failure with three raw telemetry rows, two of which describe the same batch job event.

detect-duplicate-telemetry-events.py
1raw_job_events = [ 2 {"event_id": "job-001", "job_id": "batch-100", "type": "started"}, 3 {"event_id": "job-001", "job_id": "batch-100", "type": "started"}, 4 {"event_id": "job-002", "job_id": "batch-101", "type": "started"}, 5] 6unique_events = { 7 event["event_id"]: event 8 for event in raw_job_events 9} 10deduplication_policy = "telemetry-event-id-v1" 11duplicate_rows = len(raw_job_events) - len(unique_events) 12 13print("naive started-job count:", len(raw_job_events)) 14print("deduplicated started-job count:", len(unique_events)) 15print("duplicate rows:", duplicate_rows)
Output
1naive started-job count: 3 2deduplicated started-job count: 2 3duplicate rows: 1

An anomaly review should inspect input quality before retraining a model. Retraining on duplicated counts would teach the model to copy a pipeline bug. This tiny dictionary deduper keeps one row because the repeated payloads match. If two rows reuse an event ID but disagree, quarantine the conflict instead of silently choosing one.

Gate and publish a forecast candidate

A minimal release gate should prove time order, calibration order, normal-day accuracy, interval bookkeeping, contextualized alerts, and assigned ownership. These checks don't prove the baseline is production-ready, but they prevent avoidable mistakes from reaching a capacity workflow.

Run the gate.

check-forecast-release-gates.py
1gates = { 2 "time_order": max(time_train) < min(time_test), 3 "calibration_before_holdout": max(calibration) < min(holdout), 4 "normal_day_mae": mean_absolute_error(normal_errors) <= 3.0, 5 "calibration_coverage_recorded": 0.0 <= calibration_coverage <= 1.0, 6 "holdout_coverage_recorded": 0.0 <= holdout_coverage <= 1.0, 7 "interval_policy_versioned": bool(interval_policy), 8 "deduplication_policy_versioned": bool(deduplication_policy), 9 "alerts_have_context": all( 10 { 11 "series", "forecast_version", "training_cutoff", "interval_policy", 12 "date", "forecast", "actual", "forecast_error", "lower", "upper", 13 "launch_event", "action", "owner", "resolution", 14 } <= alert.keys() 15 for alert in alerts 16 ), 17 "owner_assigned": bool(owner), 18} 19 20for name, passed in gates.items(): 21 print(f"{name}: {passed}") 22print("release gate:", all(gates.values()))
Output
1time_order: True 2calibration_before_holdout: True 3normal_day_mae: True 4calibration_coverage_recorded: True 5holdout_coverage_recorded: True 6interval_policy_versioned: True 7deduplication_policy_versioned: True 8alerts_have_context: True 9owner_assigned: True 10release gate: True

Publish a receipt. The hash lets a later monitoring job identify the exact baseline, interval, and input policies it evaluates.

publish-forecast-candidate-receipt.py
1receipt = { 2 "series": "fc-a.batch_jobs", 3 "forecast": forecast_version, 4 "training_cutoff": training_cutoff, 5 "interval_policy": { 6 "version": interval_policy, 7 "target_coverage": target_coverage, 8 "absolute_error_band": error_band, 9 "calibration_window": [str(rows[7]["date"]), str(rows[20]["date"])], 10 "calibration_coverage": round(calibration_coverage, 3), 11 "holdout_window": [str(rows[21]["date"]), str(rows[27]["date"])], 12 "holdout_coverage": round(holdout_coverage, 3), 13 }, 14 "input_policy": { 15 "deduplication": deduplication_policy, 16 }, 17 "evaluation": { 18 "normal_day_mae": round(mean_absolute_error(normal_errors), 1), 19 "alert_count": len(alerts), 20 }, 21 "alerts": alerts, 22 "release_checks": gates, 23 "owner": owner, 24 "status": "candidate_for_shadow" if all(gates.values()) else "blocked", 25} 26payload = dumps(receipt, sort_keys=True, separators=(",", ":")) 27 28print(dumps(receipt, indent=2, sort_keys=True)) 29print("receipt sha256:", sha256(payload.encode()).hexdigest()[:12])
Output
1{ 2 "alerts": [ 3 { 4 "action": "review", 5 "actual": 160, 6 "date": "2026-01-30", 7 "forecast": 132, 8 "forecast_error": 28, 9 "forecast_version": "weekly-lag-v1", 10 "interval_policy": "empirical-absolute-error-p95-v1", 11 "launch_event": true, 12 "lower": 129, 13 "owner": "capacity-ops", 14 "resolution": "pending", 15 "series": "fc-a.batch_jobs", 16 "training_cutoff": "2026-01-25", 17 "upper": 135 18 } 19 ], 20 "evaluation": { 21 "alert_count": 1, 22 "normal_day_mae": 1.5 23 }, 24 "forecast": "weekly-lag-v1", 25 "input_policy": { 26 "deduplication": "telemetry-event-id-v1" 27 }, 28 "interval_policy": { 29 "absolute_error_band": 3, 30 "calibration_coverage": 1.0, 31 "calibration_window": [ 32 "2026-01-12", 33 "2026-01-25" 34 ], 35 "holdout_coverage": 0.857, 36 "holdout_window": [ 37 "2026-01-26", 38 "2026-02-01" 39 ], 40 "target_coverage": 0.95, 41 "version": "empirical-absolute-error-p95-v1" 42 }, 43 "owner": "capacity-ops", 44 "release_checks": { 45 "alerts_have_context": true, 46 "calibration_before_holdout": true, 47 "calibration_coverage_recorded": true, 48 "deduplication_policy_versioned": true, 49 "holdout_coverage_recorded": true, 50 "interval_policy_versioned": true, 51 "normal_day_mae": true, 52 "owner_assigned": true, 53 "time_order": true 54 }, 55 "series": "fc-a.batch_jobs", 56 "status": "candidate_for_shadow", 57 "training_cutoff": "2026-01-25" 58} 59receipt sha256: a40b84aed22b

The receipt keeps calibration evidence separate from later holdout evidence and binds the policies that produced both. Passing this tiny fixture earns shadow evaluation, not a silent production rollout.

Explain the alert without looking back

Explain the complete path in your own words:

  1. Why does a daily batch job series need chronological splits?
  2. What repeated pattern does a same-weekday baseline capture?
  3. Why is January 30's +28 a forecast error rather than a training residual?
  4. What does the empirical ±3 band mean, and what doesn't it guarantee?
  5. Why do alert precision and recall need a separate reviewed dataset?
  6. Why should input deduplication happen before model retraining?

Practice

  1. Change Friday's observed volume from 160 to 134. Predict whether the alert fires before rerunning the lab.
  2. Change probability=0.95 to probability=0.50. Explain why the narrower band sends more rows to review.
  3. Add a second model-serving cluster with a different weekend pattern. Explain why a shared global error band may hide local problems.
  4. Add a two-day forecast horizon. Identify which earlier observations each prediction may use.
  5. Extend resolved_events with two noisy alerts. Compute the new alert precision before running the cell.

Practice answer sketches

PromptReasoning check
Friday becomes 134Forecast error becomes 134 - 132 = 2, which stays inside the ±3 band. No alert fires.
Quantile becomes 0.50Band shrinks from ±3 to ±2. Tuesday's +3 and Friday's +28 now enter review.
Add another model-serving clusterCalibrate and report by cluster before sharing a band. Different local schedules can disappear inside an aggregate.
Predict two days aheadFor a target at time ttt, use information available no later than t−2t - 2t−2. Recompute each fold with that cutoff.
Add two noisy alertsTrue positives stay at 2, false positives rise from 1 to 3, and precision falls to 2 / 5 = 0.4.

What strong answers show

EvidenceA strong explanation demonstrates
temporal splitproves each evaluated day occurs after its training and calibration inputs
baselinecomputes a same-weekday forecast and beats it before proposing extra model complexity
error metriccalculates MAE in batch jobs and discusses asymmetric capacity cost
uncertaintytreats the tiny empirical band as a teaching device and measures rolling-window coverage in production
anomaly handlingstores reproducible alert context and separates unusual observations from root causes
alert evaluationmeasures reviewed alert precision and recall independently from forecast MAE
data qualitychecks duplicate or stale inputs before retraining from anomalous observations

When the policy breaks

SymptomCauseFix
Test results collapse at launchrandom split leaked future behavioruse chronological rolling-origin validation
Alerts fire every Mondayweekly seasonality is absentadd a same-weekday baseline first
Interval looks precise but misses peaksband was calibrated in sample or on too little historymeasure out-of-sample coverage by horizon and slice
Promotion creates an incident pagealert threshold has no business contextroute known events to capacity review
Model learns a fake demand jumpduplicated events entered training rowsdeduplicate and audit ingestion before retraining
Operations ignores alertsreceipt lacks ownership or resolutionlog policy version, context, owner, and outcome
Complete the lesson

Mastery Check

Answer every question, then check your score. Score above 75% to mark this lesson complete.

1.A forecast split for one cluster has latest_train=2026-01-31 and earliest_test=2026-01-06. What does this imply?
2.For a daily batch job series with weekly seasonality, the target is Tuesday Jan 27. Last Tuesday's volume was 110 and the actual target count is 113. Under a seasonal naive weekly-lag baseline, what forecast error is recorded?
3.A holdout week's forecast errors are [-1, 3, -2, 1, 28, 1, -1], and the +28 launch_event day may have a higher underforecasting cost than an equal overforecast. Which report is valid?
4.A team adds a two-day forecast horizon for daily batch-job counts and wants to backtest it. Which evaluation setup preserves the required time order?
5.A forecast for Jan 30 was issued before Jan 30 using a same-weekday value of 132. The observed count later became 160. How should the +28 be recorded?
6.An empirical interval policy uses 14 earlier absolute one-day errors and selects a +/-3 batch job band for target coverage 0.95. Calibration coverage is 1.0, but later holdout coverage is 6/7. Which conclusion is valid?
7.A Friday count of 160 is compared with a forecast of 132 and a +/-3 batch job band. The row has launch_event=True. What should the alert policy do?
8.Five reviewed events have (alert, actionable) values: (True, True), (True, False), (False, True), (False, False), (True, True). What are the alert precision and alert recall?
9.An anomaly review finds three raw telemetry rows, but two rows share event_id job-001 and the same batch job payload. What is the safest next step before retraining?
10.A forecast candidate has acceptable normal-day MAE, but its interval policy version is blank and alert records omit owner and resolution fields. What should the release gate do?

10 questions remaining.

Next Step
Continue to Monitoring Predictive Models

You can issue a time-aware forecast candidate and turn unusual future errors into reviewable alerts. Next, monitor deployed models for <span data-glossary="data-drift">input shift</span>, delayed outcome degradation, and retraining decisions.

PreviousRanking and Recommendation Systems
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Forecasting: Principles and Practice, Third Edition.

Hyndman, R. J. & Athanasopoulos, G. · 2021

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.