agnes-the-ai-analyst/services/telegram_bot/test_report.py
Vojtech 38f6b639d2
feat(observability): request_id end-to-end + dev debug toolbar + centralized logging (#136)
Cuts release 0.20.0.

## Highlights
- X-Request-ID header on every response + sanitized to [A-Za-z0-9_-] (CRLF log-forging mitigation)
- Error pages (HTML + JSON 500) surface request_id for support tickets
- Dev debug toolbar gated by DEBUG=1 — fastapi-debug-toolbar with custom DuckDBPanel
- Centralized app.logging_config.setup_logging() replaces 23 scattered basicConfig calls
- Telegram bot drops bot.log file — stdout only (BREAKING)

## Devin findings addressed
- BUG_0001: .env.template no longer claims FastAPI debug=True
- BUG_0002: subprocess extractor logs INFO to stderr again
- ANALYSIS_0003: _wants_html no longer matches Accept: */* (curl gets JSON as before)
- BUG on b1c6ee9: HTML 500 page no longer leaks str(exc) in production
- BUG on b13d2fe: 2 CLAUDE.md compliance flags (transform.py + ws_gateway) accepted as scope-limited logging refactor — follow-up to update CLAUDE.md if needed

See CHANGELOG [0.20.0] for full notes.
2026-04-29 22:54:21 +02:00

249 lines
6.7 KiB
Python

"""
Generate a sample report chart for /test command.
Creates a polished demo dashboard image with fake data
to showcase what notifications can look like.
"""
import os
import random
import tempfile
from datetime import datetime, timedelta
import numpy as np
# Brand colors
COLOR_PRIMARY = "#0073D1"
COLOR_DARK = "#1A253C"
COLOR_SUCCESS = "#10B77F"
COLOR_WARNING = "#F59F0A"
COLOR_ERROR = "#EA580C"
COLOR_BG = "#F5F7FA"
COLOR_SURFACE = "#FFFFFF"
COLOR_GRAY = "#6B7280"
def generate_test_report(username: str) -> tuple[str, str]:
"""Generate a test report image and return (image_path, caption).
Creates a professional-looking dashboard with:
- Revenue trend line (7 days)
- KPI summary bar
- Top metrics breakdown
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import FancyBboxPatch
rng = random.Random(42)
# Generate fake data - 14 days of revenue
today = datetime.now()
dates = [today - timedelta(days=i) for i in range(13, -1, -1)]
base_revenue = 52000
revenues = [base_revenue + rng.gauss(0, 5000) + i * 300 for i, _ in enumerate(dates)]
# Make today slightly lower for "alert" feel
revenues[-1] = revenues[-2] * 0.82
# KPI data
today_rev = revenues[-1]
yesterday_rev = revenues[-2]
week_avg = np.mean(revenues[-7:])
total_7d = sum(revenues[-7:])
change_pct = ((today_rev - yesterday_rev) / yesterday_rev) * 100
# Create figure with subplots
fig = plt.figure(figsize=(10, 7), facecolor=COLOR_BG)
fig.subplots_adjust(top=0.88, bottom=0.08, left=0.08, right=0.95, hspace=0.45)
# Title
fig.text(
0.08,
0.95,
"Data Analyst Report",
fontsize=18,
fontweight="bold",
color=COLOR_DARK,
fontfamily="sans-serif",
)
fig.text(
0.08,
0.91,
f"{today.strftime('%B %d, %Y')} | Demo report for {username}",
fontsize=11,
color=COLOR_GRAY,
fontfamily="sans-serif",
)
# --- KPI Cards (top row) ---
kpi_data = [
("Today's Revenue", f"${today_rev:,.0f}", f"{change_pct:+.1f}%", change_pct >= 0),
("7-Day Average", f"${week_avg:,.0f}", "", True),
("7-Day Total", f"${total_7d:,.0f}", "", True),
("Active Projects", f"{rng.randint(180, 220)}", "+12", True),
]
for i, (label, value, badge, is_positive) in enumerate(kpi_data):
ax_kpi = fig.add_axes([0.08 + i * 0.225, 0.72, 0.19, 0.14])
ax_kpi.set_xlim(0, 1)
ax_kpi.set_ylim(0, 1)
ax_kpi.axis("off")
# Card background
card = FancyBboxPatch(
(0, 0),
1,
1,
boxstyle="round,pad=0.05",
facecolor=COLOR_SURFACE,
edgecolor="#E5E7EB",
linewidth=1,
)
ax_kpi.add_patch(card)
ax_kpi.text(0.1, 0.7, label, fontsize=8, color=COLOR_GRAY, fontfamily="sans-serif")
ax_kpi.text(0.1, 0.25, value, fontsize=16, fontweight="bold", color=COLOR_DARK, fontfamily="sans-serif")
if badge:
badge_color = COLOR_SUCCESS if is_positive else COLOR_ERROR
ax_kpi.text(
0.9, 0.25, badge, fontsize=9, fontweight="bold", color=badge_color, ha="right", fontfamily="sans-serif"
)
# --- Revenue Chart ---
ax_chart = fig.add_subplot(2, 1, 2)
ax_chart.set_facecolor(COLOR_SURFACE)
# Plot area fill
ax_chart.fill_between(
dates,
revenues,
alpha=0.1,
color=COLOR_PRIMARY,
)
# Main line
ax_chart.plot(
dates[:-1],
revenues[:-1],
color=COLOR_PRIMARY,
linewidth=2.5,
solid_capstyle="round",
)
# Today's point (highlighted)
ax_chart.plot(
dates[-1],
revenues[-1],
"o",
color=COLOR_ERROR,
markersize=8,
zorder=5,
)
ax_chart.plot(
dates[-1],
revenues[-1],
"o",
color=COLOR_ERROR,
markersize=14,
alpha=0.2,
zorder=4,
)
# Dashed line connecting to today
ax_chart.plot(
dates[-2:],
revenues[-2:],
color=COLOR_ERROR,
linewidth=2,
linestyle="--",
alpha=0.7,
)
# Average line
ax_chart.axhline(
y=week_avg,
color=COLOR_WARNING,
linewidth=1,
linestyle=":",
alpha=0.8,
label=f"7d avg: ${week_avg:,.0f}",
)
# Styling
ax_chart.set_title(
"Daily Revenue (14 days)",
fontsize=13,
fontweight="bold",
color=COLOR_DARK,
loc="left",
pad=12,
fontfamily="sans-serif",
)
ax_chart.xaxis.set_major_formatter(mdates.DateFormatter("%b %d"))
ax_chart.xaxis.set_major_locator(mdates.DayLocator(interval=2))
plt.setp(ax_chart.xaxis.get_majorticklabels(), rotation=0, fontsize=9, color=COLOR_GRAY)
plt.setp(ax_chart.yaxis.get_majorticklabels(), fontsize=9, color=COLOR_GRAY)
ax_chart.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f"${x / 1000:.0f}k"))
ax_chart.spines["top"].set_visible(False)
ax_chart.spines["right"].set_visible(False)
ax_chart.spines["left"].set_color("#E5E7EB")
ax_chart.spines["bottom"].set_color("#E5E7EB")
ax_chart.tick_params(colors="#E5E7EB")
ax_chart.grid(axis="y", color="#F3F4F6", linewidth=0.8)
ax_chart.legend(
loc="upper left",
fontsize=9,
frameon=False,
labelcolor=COLOR_GRAY,
)
# Annotate today's drop
ax_chart.annotate(
f"${today_rev:,.0f}\n({change_pct:+.1f}%)",
xy=(dates[-1], revenues[-1]),
xytext=(30, 25),
textcoords="offset points",
fontsize=9,
fontweight="bold",
color=COLOR_ERROR,
fontfamily="sans-serif",
arrowprops=dict(arrowstyle="->", color=COLOR_ERROR, lw=1.2),
bbox=dict(boxstyle="round,pad=0.4", facecolor="white", edgecolor=COLOR_ERROR, alpha=0.9),
)
# Footer
fig.text(
0.5,
0.01,
"This is a demo report. Set up real notifications with your AI assistant.",
fontsize=8,
color=COLOR_GRAY,
ha="center",
fontstyle="italic",
fontfamily="sans-serif",
)
# Save
chart_path = os.path.join(
tempfile.gettempdir(),
f"notify_test_{username}_{datetime.now():%Y%m%d%H%M%S}.png",
)
fig.savefig(chart_path, dpi=180, bbox_inches="tight", facecolor=COLOR_BG)
plt.close(fig)
caption = (
f"*Test Report for {username}*\n"
f"Revenue today: ${today_rev:,.0f} ({change_pct:+.1f}%)\n"
f"7d avg: ${week_avg:,.0f}\n\n"
f"This is a demo. Ask your AI assistant to create real notifications!"
)
return chart_path, caption