287 lines
9.5 KiB
Python
287 lines
9.5 KiB
Python
|
|
"""
|
||
|
|
Notification Service Analytics API Endpoints
|
||
|
|
Professional/Enterprise tier analytics for notification performance and delivery metrics
|
||
|
|
"""
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
||
|
|
from typing import Optional, Dict, Any
|
||
|
|
from uuid import UUID
|
||
|
|
from datetime import datetime
|
||
|
|
import structlog
|
||
|
|
|
||
|
|
from app.core.database import get_db
|
||
|
|
from shared.auth.decorators import get_current_user_dep
|
||
|
|
from shared.auth.access_control import analytics_tier_required
|
||
|
|
from shared.routing.route_builder import RouteBuilder
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
logger = structlog.get_logger()
|
||
|
|
route_builder = RouteBuilder('notification')
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("delivery-stats"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_delivery_statistics(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(30, ge=1, le=365),
|
||
|
|
notification_type: Optional[str] = Query(None),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze notification delivery statistics (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"notification_type": notification_type,
|
||
|
|
"total_sent": 0,
|
||
|
|
"delivery_stats": {
|
||
|
|
"delivered": 0,
|
||
|
|
"failed": 0,
|
||
|
|
"pending": 0,
|
||
|
|
"cancelled": 0,
|
||
|
|
"delivery_rate": 0.0
|
||
|
|
},
|
||
|
|
"channel_breakdown": {
|
||
|
|
"email": {"sent": 0, "delivered": 0, "failed": 0},
|
||
|
|
"whatsapp": {"sent": 0, "delivered": 0, "failed": 0},
|
||
|
|
"push": {"sent": 0, "delivered": 0, "failed": 0}
|
||
|
|
},
|
||
|
|
"daily_trends": [],
|
||
|
|
"average_delivery_time_seconds": 0.0
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get delivery stats", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get delivery stats: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("engagement-metrics"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_engagement_metrics(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(30, ge=1, le=365),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze user engagement with notifications (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"engagement_stats": {
|
||
|
|
"total_notifications": 0,
|
||
|
|
"read_notifications": 0,
|
||
|
|
"unread_notifications": 0,
|
||
|
|
"read_rate": 0.0,
|
||
|
|
"average_time_to_read_minutes": 0.0
|
||
|
|
},
|
||
|
|
"user_engagement": {
|
||
|
|
"active_users": 0,
|
||
|
|
"inactive_users": 0,
|
||
|
|
"highly_engaged_users": 0
|
||
|
|
},
|
||
|
|
"type_engagement": [],
|
||
|
|
"hourly_engagement_pattern": [],
|
||
|
|
"day_of_week_pattern": []
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get engagement metrics", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get engagement metrics: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("failure-analysis"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_failure_analysis(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(7, ge=1, le=90),
|
||
|
|
channel: Optional[str] = Query(None),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze notification failures and errors (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"channel": channel,
|
||
|
|
"total_failures": 0,
|
||
|
|
"failure_rate": 0.0,
|
||
|
|
"failure_breakdown": {
|
||
|
|
"invalid_recipient": 0,
|
||
|
|
"service_error": 0,
|
||
|
|
"network_error": 0,
|
||
|
|
"rate_limit": 0,
|
||
|
|
"authentication_error": 0,
|
||
|
|
"other": 0
|
||
|
|
},
|
||
|
|
"common_errors": [],
|
||
|
|
"error_trends": [],
|
||
|
|
"retry_success_rate": 0.0,
|
||
|
|
"recommendations": []
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get failure analysis", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get failure analysis: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("template-performance"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_template_performance(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(30, ge=1, le=365),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze notification template performance (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"templates": [],
|
||
|
|
"performance_metrics": {
|
||
|
|
"best_performing_template": None,
|
||
|
|
"worst_performing_template": None,
|
||
|
|
"average_delivery_rate": 0.0,
|
||
|
|
"average_read_rate": 0.0
|
||
|
|
},
|
||
|
|
"optimization_suggestions": []
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get template performance", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get template performance: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("channel-comparison"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_channel_comparison(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(30, ge=1, le=365),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Compare performance across notification channels (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"channels": {
|
||
|
|
"email": {
|
||
|
|
"sent": 0,
|
||
|
|
"delivered": 0,
|
||
|
|
"read": 0,
|
||
|
|
"failed": 0,
|
||
|
|
"delivery_rate": 0.0,
|
||
|
|
"read_rate": 0.0,
|
||
|
|
"avg_delivery_time_seconds": 0.0,
|
||
|
|
"cost_per_notification": 0.0
|
||
|
|
},
|
||
|
|
"whatsapp": {
|
||
|
|
"sent": 0,
|
||
|
|
"delivered": 0,
|
||
|
|
"read": 0,
|
||
|
|
"failed": 0,
|
||
|
|
"delivery_rate": 0.0,
|
||
|
|
"read_rate": 0.0,
|
||
|
|
"avg_delivery_time_seconds": 0.0,
|
||
|
|
"cost_per_notification": 0.0
|
||
|
|
},
|
||
|
|
"push": {
|
||
|
|
"sent": 0,
|
||
|
|
"delivered": 0,
|
||
|
|
"read": 0,
|
||
|
|
"failed": 0,
|
||
|
|
"delivery_rate": 0.0,
|
||
|
|
"read_rate": 0.0,
|
||
|
|
"avg_delivery_time_seconds": 0.0,
|
||
|
|
"cost_per_notification": 0.0
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"recommendations": {
|
||
|
|
"most_reliable_channel": None,
|
||
|
|
"fastest_channel": None,
|
||
|
|
"most_cost_effective_channel": None,
|
||
|
|
"best_engagement_channel": None
|
||
|
|
}
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get channel comparison", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get channel comparison: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("user-preferences-insights"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_user_preferences_insights(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze user notification preferences patterns (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"total_users": 0,
|
||
|
|
"preference_distribution": {
|
||
|
|
"email_enabled": 0,
|
||
|
|
"whatsapp_enabled": 0,
|
||
|
|
"push_enabled": 0,
|
||
|
|
"all_channels_enabled": 0,
|
||
|
|
"no_channels_enabled": 0
|
||
|
|
},
|
||
|
|
"popular_notification_types": [],
|
||
|
|
"opt_out_trends": [],
|
||
|
|
"channel_preferences": {
|
||
|
|
"email_only": 0,
|
||
|
|
"whatsapp_only": 0,
|
||
|
|
"push_only": 0,
|
||
|
|
"multi_channel": 0
|
||
|
|
},
|
||
|
|
"recommendations": []
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get preferences insights", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get preferences insights: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_analytics_route("cost-analysis"),
|
||
|
|
response_model=dict
|
||
|
|
)
|
||
|
|
@analytics_tier_required
|
||
|
|
async def get_cost_analysis(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
days: int = Query(30, ge=1, le=365),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db=Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Analyze notification delivery costs (Professional/Enterprise)"""
|
||
|
|
try:
|
||
|
|
return {
|
||
|
|
"period_days": days,
|
||
|
|
"total_cost": 0.0,
|
||
|
|
"cost_by_channel": {
|
||
|
|
"email": 0.0,
|
||
|
|
"whatsapp": 0.0,
|
||
|
|
"push": 0.0
|
||
|
|
},
|
||
|
|
"cost_by_type": [],
|
||
|
|
"volume_vs_cost_trends": [],
|
||
|
|
"cost_optimization_suggestions": [],
|
||
|
|
"projected_monthly_cost": 0.0,
|
||
|
|
"cost_per_user": 0.0
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get cost analysis", error=str(e), tenant_id=tenant_id)
|
||
|
|
raise HTTPException(status_code=500, detail=f"Failed to get cost analysis: {str(e)}")
|