Build a recurring report pipeline: collect inputs, aggregate over daily/weekly/monthly windows, template the body, deliver via email/chat/file, and stay silent on no-change days. Triggers on: scheduled report, periodic summary, weekly digest, daily status report.
./install.sh scheduled-report-generationname: "scheduled_report_generation" description: "Build a recurring report pipeline: collect inputs, aggregate over daily/weekly/monthly windows, template the body, deliver via email/chat/file, and stay silent on no-change days. Triggers on: scheduled report, periodic summary, weekly digest, daily status report." metadata: { "includeInPrompt": true }
Turn raw events into a report that arrives on schedule, reads the same way every time, and does not arrive at all when nothing changed. This owns the pipeline: collect, aggregate, template, deliver, silence check. Nothing else.
Not this skill: cron-discipline owns job safety (overlap locks, idempotency, retry policy) and metric-definitions owns what to measure (definitions, naming, traps). Point there when the problem is scheduling mechanics or metric design. This skill starts once the metrics exist and the job runs.
Every run produces exactly one of:
silent: no change with run time and the figures that matched. Nothing sent to any channel.Both outcomes get logged. A silent day that nobody can see looks identical to a broken job.
Save this as report.py and run it with a data file. Stdlib only, no installs.
python
"""report.py: minimal recurring-report pipeline (stdlib only).
Usage:
python3 report.py data.json --window daily|weekly|monthly [--out report.txt]
Reads events like: [{"date": "2026-10-07", "name": "signups", "value": 42}, ...]
Keeps state in state.json next to the script. Applies the stay-silent rule.
"""
import argparse, json, datetime, sys
WINDOWS = {"daily": 1, "weekly": 7, "monthly": 30}
TEMPLATE = (
"{title}\n"
"Period: {start} to {end} ({window})\n"
"Events: {count}\n"
"Total: {total}\n"
"Avg per day: {avg_per_day:.1f}\n"
"Top item: {top_item}\n"
"Generated: {generated}\n"
)
def aggregate(events, days, end):
start = end - datetime.timedelta(days=days)
in_window = [
e for e in events
if start <= datetime.date.fromisoformat(e["date"]) <= end
]
total = sum(e["value"] for e in in_window)
by_name = {}
for e in in_window:
by_name[e["name"]] = by_name.get(e["name"], 0) + e["value"]
top = max(by_name, key=by_name.get) if by_name else "none"
return {
"count": len(in_window),
"total": total,
"avg_per_day": round(total / days, 1) if days else 0,
"top_item": top,
"start": str(start),
"end": str(end),
}
def main():
p = argparse.ArgumentParser()
p.add_argument("data")
p.add_argument("--window", default="daily", choices=WINDOWS)
p.add_argument("--out")
p.add_argument("--title", default="Periodic Report")
p.add_argument("--state", default="state.json")
args = p.parse_args()
with open(args.data) as f:
events = json.load(f)
end = max(datetime.date.fromisoformat(e["date"]) for e in events)
agg = aggregate(events, WINDOWS[args.window], end)
key_figures = {"window": args.window, "count": agg["count"],
"total": agg["total"], "top_item": agg["top_item"]}
try:
with open(args.state) as f:
prev = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
prev = {}
if prev.get("last") == key_figures:
print("SILENT: no change since last report. Skipping delivery.")
return 0
body = TEMPLATE.format(title=args.title, window=args.window,
generated=datetime.datetime.now().isoformat(timespec="seconds"),
**agg)
if args.out:
with open(args.out, "w") as f:
f.write(body)
print(f"Report written to {args.out}")
else:
print(body, end="")
with open(args.state, "w") as f:
json.dump({"last": key_figures}, f, indent=2)
return 0
if __name__ == "__main__":
sys.exit(main())
Example data file data.json:
json
[
{"date": "2026-10-07", "name": "signups", "value": 42},
{"date": "2026-10-07", "name": "sales", "value": 5},
{"date": "2026-10-06", "name": "signups", "value": 10},
{"date": "2026-09-30", "name": "signups", "value": 88}
]
Run: python3 report.py data.json --window weekly. Run it twice; the second run prints the silent line.
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.