agnes-the-ai-analyst/examples/notifications/metric_report.py
Petr c56905d34f Initial commit: OSS data distribution platform
Open-source AI data analyst platform extracted from internal repo.
Includes data sync engine, Keboola adapter, Flask web portal,
server deployment scripts, and configuration templates.
2026-03-08 23:31:28 +01:00

125 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""
Example notification: Daily metric report with chart image.
Generates a summary chart using matplotlib and sends it as a Telegram photo.
Outputs JSON to stdout for notify-runner.
"""
import json
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path
import duckdb
DB_PATH = Path.home() / "user" / "duckdb" / "analytics.duckdb"
def generate_chart(data: list[tuple]) -> str | None:
"""Generate a bar chart and return the file path."""
try:
import matplotlib
matplotlib.use("Agg") # Non-interactive backend
import matplotlib.pyplot as plt
dates = [row[0].strftime("%m/%d") for row in data]
values = [float(row[1]) for row in data]
fig, ax = plt.subplots(figsize=(8, 4))
bars = ax.bar(dates, values, color="#0073D1", width=0.6)
# Highlight today
if len(bars) > 0:
bars[-1].set_color("#EA580C")
ax.set_title("Daily Revenue - Last 7 Days", fontsize=14, fontweight="bold")
ax.set_ylabel("Revenue ($)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.xticks(rotation=45, ha="right")
plt.tight_layout()
# Save to temp file
chart_path = os.path.join(
tempfile.gettempdir(),
f"notify_{os.environ.get('USER', 'user')}_metric_{datetime.now():%Y%m%d}.png",
)
plt.savefig(chart_path, dpi=150, bbox_inches="tight")
plt.close()
return chart_path
except ImportError:
print("matplotlib not installed, skipping chart", file=sys.stderr)
return None
except Exception as e:
print(f"Chart generation error: {e}", file=sys.stderr)
return None
def build_report() -> dict:
"""Build daily metric report."""
if not DB_PATH.exists():
return {"notify": False}
try:
conn = duckdb.connect(str(DB_PATH), read_only=True)
# TODO: Adapt this query to your schema.
# DuckDB views use relative paths, so scripts must run from ~/
# (notify-runner sets cwd to home directory automatically).
#
# Example for a table with date + numeric columns:
# SELECT DATE_TRUNC('day', created_at)::DATE AS day,
# SUM(amount) AS revenue
# FROM my_table
# WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'
# GROUP BY 1 ORDER BY 1
rows = conn.execute("""
SELECT
DATE_TRUNC('day', created_at)::DATE AS day,
COUNT(*) AS cnt
FROM kbc_project
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY 1
ORDER BY 1
""").fetchall()
conn.close()
if not rows:
return {"notify": False}
today_val = float(rows[-1][1]) if rows else 0
total_7d = sum(float(r[1]) for r in rows)
chart_path = generate_chart(rows)
result = {
"notify": True,
"title": "Daily Metric Report",
"message": (
f"Today: {today_val:,.0f}\n"
f"7d total: {total_7d:,.0f}\n"
f"7d avg: {total_7d / len(rows):,.0f}"
),
"cooldown": "1d",
}
if chart_path:
result["image_path"] = chart_path
return result
except Exception as e:
print(f"metric_report error: {e}", file=sys.stderr)
return {"notify": False}
if __name__ == "__main__":
result = build_report()
print(json.dumps(result))