Schedule a Python report with observable failures
In this article (6 sections)
A scheduler starting a process does not establish that the report succeeded. The process must return a meaningful exit status, retain an attempt log and produce a verifiable output artifact. Monitoring also needs to detect the case where the process never starts.
Test the command independently before configuring a schedule. A script that works only from your terminal's current directory or interactive environment is not ready for unattended execution.
Use an observable local wrapper
The original report automation lab includes scheduled_run.py. It launches the report with the same Python interpreter, absolute input and output paths, a fixed working directory and a timeout.
Each attempt gets a separate log containing start, child output and finish records. The wrapper preserves the child's exit code. A timeout returns 124 under this local contract. It does not register a real scheduled task or send an external alert.
Verify both success and failure
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from scheduled_run import run_scheduled
config = json.loads(Path('report-config.json').read_text())
with TemporaryDirectory(prefix='scheduled-report-example-') as temporary:
root = Path(temporary)
config_path = root/'config.json'
config_path.write_text(json.dumps(config),encoding='utf-8')
success,first_log = run_scheduled(config_path,root/'runs',root/'logs')
assert success==0
first = [json.loads(line) for line in first_log.read_text().splitlines()]
assert first[0]['event']=='scheduled_attempt_started'
assert first[-1]['exit_code']==0
assert any(row['event']=='report_prepared' for row in first)
config_path.write_text(json.dumps(dict(config,region='INVALID')),encoding='utf-8')
failure,second_log = run_scheduled(config_path,root/'runs',root/'logs')
assert failure==1 and second_log!=first_log
second = [json.loads(line) for line in second_log.read_text().splitlines()]
assert any(row.get('code')=='invalid_region' for row in second)
assert second[-1]['exit_code']==1
print({'successful_attempt_logged':True,'failed_attempt_nonzero':True})The failure is deliberate and local. It confirms that an invalid reporting parameter cannot become a success merely because Python launched correctly.
The wrapper logs the child output rather than environment variables or source rows. A production log policy should similarly avoid recording credentials and unnecessary sensitive data.
Configure the scheduler around the tested command
For Windows Task Scheduler, use the intended environment's absolute Python executable as the program and pass the wrapper path plus explicit config, output and log arguments. Configure the working account, trigger, timeout and overlapping-run behavior deliberately.
Microsoft's scheduled-task documentation describes task registration and execution context. Verify the account can read the source and write the chosen directories when running without an interactive session.
The supplied configuration describes one fixed January reporting period. Scheduling that same configuration every week would correctly rerun January, not automatically advance the report. A real recurring workflow must materialize and record the appropriate period-specific configuration before invoking the reporting command.
Monitor the absence of an expected run
A child exit code can report failure only if the child starts. A disabled task, sleeping machine, expired credential or unavailable host may produce no application log at all.
Maintain an independent expectation for when a completed report should exist. Check for a verified artifact for the intended period, its freshness and the expected run status. A recent log file for a different period should not satisfy that check.
The lab does not implement external monitoring or notifications. Its local tests establish command behavior, not unattended reliability in a deployed environment.
Preserve a clear retry decision
If failure is a stale source watermark, rerunning immediately may not help. If it is a temporary file-access issue, a bounded retry may be appropriate. Keep failure codes specific enough to distinguish these cases and retain every attempt log.
Use the safe-rerun design so retrying report preparation does not overwrite a prior reviewed bundle. Distribution remains a separate action with its own approval and delivery controls.
Exercise: run the wrapper from a different directory and verify the same result. Then describe how your monitoring would detect a scheduled time that passes without any attempt log, rather than only monitoring explicit failures.
NeuraPath's Data Analytics with Generative AI course connects Python automation with operational handover. A useful schedule makes successful output and failed or absent runs distinguishable.
Continue learning
This article is part of the Reliable reporting automation sequence. Use the neighbouring tasks when you need the prerequisite or the next application.
- Review the prerequisite or neighbouring task in Detect incomplete source files before generating a report.
- Continue with Create a report manifest with timestamps and row counts.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI