"""Reports Handler - OpenPanel analytics reports (8 tools)
Note: project_id is optional if configured in environment variables.
"""
import json
from typing import Any
from plugins.openpanel.client import OpenPanelClient
from plugins.openpanel.handlers.utils import get_project_id as _get_project_id
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (8 tools)"""
return [
{
"name": "get_overview_report",
"method_name": "get_overview_report",
"description": "Get overview statistics including total events, users, sessions, and page views. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"date_range": {
"type": "string",
"description": "Date range. Common: today, yesterday, 7d, 14d, 30d, 60d, 90d, 6m, 12m, monthToDate, yearToDate, all",
"default": "30d",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_retention_report",
"method_name": "get_retention_report",
"description": "Get user retention analysis showing how many users return over time. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"start_event": {
"type": "string",
"description": "Event that marks user acquisition (e.g., 'signup_completed')",
"default": "session_start",
},
"return_event": {
"type": "string",
"description": "Event that marks user return (e.g., 'app_opened')",
"default": "session_start",
},
"period": {
"type": "string",
"enum": ["day", "week", "month"],
"description": "Retention period granularity",
"default": "week",
},
"cohorts": {
"type": "integer",
"description": "Number of cohorts to analyze",
"default": 8,
"maximum": 12,
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_cohort_report",
"method_name": "get_cohort_report",
"description": "Get cohort analysis grouping users by acquisition date and tracking their behavior. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"cohort_type": {
"type": "string",
"enum": ["first_seen", "signup", "custom"],
"description": "How to define cohorts",
"default": "first_seen",
},
"cohort_event": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Custom event for cohort definition (when cohort_type is 'custom')",
},
"measure_event": {
"type": "string",
"description": "Event to measure for each cohort",
"default": "session_start",
},
"date_range": {
"type": "string",
"description": "Date range. Common: 7d, 14d, 30d, 60d, 90d, 6m, 12m, yearToDate, all",
"default": "6m",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_paths_report",
"method_name": "get_paths_report",
"description": "Get user flow/paths analysis showing common navigation patterns. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"start_event": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Starting event for path analysis",
},
"end_event": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Ending event for path analysis",
},
"max_steps": {
"type": "integer",
"description": "Maximum path steps to analyze",
"default": 5,
"maximum": 10,
},
"date_range": {
"type": "string",
"description": "Date range. Common: today, yesterday, 7d, 14d, 30d, 60d, 90d, 6m, 12m, all",
"default": "30d",
},
},
"required": [],
},
"scope": "read",
},
{
"name": "get_realtime_stats",
"method_name": "get_realtime_stats",
"description": "Get real-time visitor statistics for the last 30 minutes. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
}
},
"required": [],
},
"scope": "read",
},
{
"name": "get_ab_test_results",
"method_name": "get_ab_test_results",
"description": "Get A/B test results comparing variants by conversion rate. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"test_name": {"type": "string", "description": "Name of the A/B test"},
"conversion_event": {
"type": "string",
"description": "Event that marks conversion",
},
"date_range": {
"type": "string",
"description": "Date range. Common: today, yesterday, 7d, 14d, 30d, 60d, 90d, 6m, 12m, all",
"default": "30d",
},
},
"required": ["test_name", "conversion_event"],
},
"scope": "read",
},
{
"name": "create_scheduled_report",
"method_name": "create_scheduled_report",
"description": "Create a scheduled report to be sent via email. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"name": {"type": "string", "description": "Report name"},
"report_type": {
"type": "string",
"enum": ["overview", "events", "funnels", "retention"],
"description": "Type of report",
},
"schedule": {
"type": "string",
"enum": ["daily", "weekly", "monthly"],
"description": "Report schedule",
},
"recipients": {
"type": "array",
"items": {"type": "string", "format": "email"},
"description": "Email recipients",
},
},
"required": ["name", "report_type", "schedule", "recipients"],
},
"scope": "write",
},
{
"name": "export_report_pdf",
"method_name": "export_report_pdf",
"description": "Export a report as PDF format. Note: project_id is optional if configured in environment.",
"schema": {
"type": "object",
"properties": {
"project_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Project ID (optional if configured in env)",
},
"report_type": {
"type": "string",
"enum": ["overview", "events", "funnels", "retention", "paths"],
"description": "Type of report to export",
},
"date_range": {
"type": "string",
"description": "Date range. Common: today, yesterday, 7d, 14d, 30d, 60d, 90d, 6m, 12m, all",
"default": "30d",
},
},
"required": ["report_type"],
},
"scope": "read",
},
]
# =====================
# Report Functions (8)
# =====================
async def get_overview_report(
client: OpenPanelClient, project_id: str | None = None, date_range: str = "30d"
) -> str:
"""Get overview statistics using tRPC overview.stats endpoint"""
try:
effective_project_id = _get_project_id(client, project_id)
# Use the new tRPC overview.stats endpoint
result = await client.get_overview_stats(
project_id=effective_project_id, date_range=date_range
)
# Check for errors
if "error" in result:
return json.dumps(
{
"success": False,
"project_id": effective_project_id,
"date_range": date_range,
"error": result.get("error"),
"note": "tRPC overview.stats endpoint may require authentication or the project may not exist",
},
indent=2,
ensure_ascii=False,
)
# Transform tRPC response to our format
# tRPC overview.stats returns: {current: {...}, previous: {...}}
current = result.get("current", result)
metrics = {
"unique_visitors": current.get("uniqueVisitors", 0),
"page_views": current.get("screenViews", 0),
"sessions": current.get("sessions", 0),
"bounce_rate": current.get("bounceRate", 0),
"session_duration": current.get("sessionDuration", 0),
"revenue": current.get("revenue", 0),
}
# Add previous period comparison if available
previous = result.get("previous", {})
if previous:
metrics["previous_period"] = {
"unique_visitors": previous.get("uniqueVisitors", 0),
"page_views": previous.get("screenViews", 0),
"sessions": previous.get("sessions", 0),
}
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"date_range": date_range,
"overview": metrics,
"raw": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_retention_report(
client: OpenPanelClient,
project_id: str | None = None,
start_event: str = "session_start",
return_event: str = "session_start",
period: str = "week",
cohorts: int = 8,
) -> str:
"""Get user retention analysis"""
try:
effective_project_id = _get_project_id(client, project_id)
# Note: Full retention analysis requires tRPC API access
# This provides a simplified version using export API
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"retention_config": {
"start_event": start_event,
"return_event": return_event,
"period": period,
"cohorts": cohorts,
},
"note": "Full retention analysis requires dashboard tRPC API. Use OpenPanel dashboard for detailed retention charts.",
"message": "Retention report configuration created",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_cohort_report(
client: OpenPanelClient,
project_id: str | None = None,
cohort_type: str = "first_seen",
cohort_event: str | None = None,
measure_event: str = "session_start",
date_range: str = "6m",
) -> str:
"""Get cohort analysis"""
try:
effective_project_id = _get_project_id(client, project_id)
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"cohort_config": {
"cohort_type": cohort_type,
"cohort_event": cohort_event,
"measure_event": measure_event,
"date_range": date_range,
},
"note": "Full cohort analysis requires dashboard tRPC API. Use OpenPanel dashboard for detailed cohort charts.",
"message": "Cohort report configuration created",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_paths_report(
client: OpenPanelClient,
project_id: str | None = None,
start_event: str | None = None,
end_event: str | None = None,
max_steps: int = 5,
date_range: str = "30d",
) -> str:
"""Get user flow/paths analysis"""
try:
effective_project_id = _get_project_id(client, project_id)
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"paths_config": {
"start_event": start_event,
"end_event": end_event,
"max_steps": max_steps,
"date_range": date_range,
},
"note": "Full path analysis requires dashboard tRPC API. Use OpenPanel dashboard for user flow visualization.",
"message": "Paths report configuration created",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_realtime_stats(client: OpenPanelClient, project_id: str | None = None) -> str:
"""Get real-time visitor statistics using chart.chart with 30min range"""
try:
effective_project_id = _get_project_id(client, project_id)
# Use chart.chart with 30min range for realtime data
result = await client.get_live_visitors(project_id=effective_project_id)
active_users = result.get("count", 0) if isinstance(result, dict) else 0
# Check for errors
if isinstance(result, dict) and "error" in result:
return json.dumps(
{
"success": False,
"project_id": effective_project_id,
"error": result.get("error"),
"note": "tRPC chart.chart endpoint may require authentication",
},
indent=2,
ensure_ascii=False,
)
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"realtime": {
"active_visitors_30min": active_users,
"timestamp": "now",
"period": "30min",
},
"message": "Real-time stats retrieved via chart.chart with 30min range",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_ab_test_results(
client: OpenPanelClient,
test_name: str,
conversion_event: str,
project_id: str | None = None,
date_range: str = "30d",
) -> str:
"""Get A/B test results"""
try:
effective_project_id = _get_project_id(client, project_id)
# A/B test results typically tracked via properties
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"test_name": test_name,
"ab_test_config": {"conversion_event": conversion_event, "date_range": date_range},
"note": "A/B test analysis requires tracking variants via event properties. Use OpenPanel dashboard for detailed variant comparison.",
"message": "A/B test configuration retrieved",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def create_scheduled_report(
client: OpenPanelClient,
name: str,
report_type: str,
schedule: str,
recipients: list[str],
project_id: str | None = None,
) -> str:
"""Create a scheduled report"""
try:
effective_project_id = _get_project_id(client, project_id)
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"scheduled_report": {
"name": name,
"report_type": report_type,
"schedule": schedule,
"recipients": recipients,
},
"note": "Scheduled reports require dashboard tRPC API. Configure via OpenPanel dashboard.",
"message": f"Scheduled report '{name}' configuration created",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def export_report_pdf(
client: OpenPanelClient,
report_type: str,
project_id: str | None = None,
date_range: str = "30d",
) -> str:
"""Export report as PDF"""
try:
effective_project_id = _get_project_id(client, project_id)
return json.dumps(
{
"success": True,
"project_id": effective_project_id,
"export_config": {
"report_type": report_type,
"format": "pdf",
"date_range": date_range,
},
"note": "PDF export requires dashboard functionality. Use OpenPanel dashboard to export reports.",
"message": f"PDF export configuration for {report_type} report",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)