FOUNDRY
C8 Platform
← Tasks

[CP-API-03] Dashboard stats RPC (aggregate voter/team data) (retry 1)

completedcode_genP1

Description

## CP-API-03: Dashboard stats RPC (aggregate voter/team data) Section: API | Milestone: M3 | Owner: AG Lu ### Failing Tests - **stats_api_file** (file_exists): File `campaignpro/src/api/stats.ts` must exist - **stats_sentiment** (grep): Pattern `sentiment.*count|voter_stats|getDashboardStats` in `campaignpro/src/api/stats.ts` (need 1+ matches) - **stats_activity** (grep): Pattern `recent_activity|getRecentActivity` in `campaignpro/src/api/stats.ts` (need 1+ matches) ### Acceptance Criteria - File `campaignpro/src/api/stats.ts` must exist - File matching `campaignpro/src/api/stats.ts` must contain pattern `sentiment.*count|voter_stats|getDashboardStats` - File matching `campaignpro/src/api/stats.ts` must contain pattern `recent_activity|getRecentActivity` ### Codebase Conventions - Trilingual comments: // EN: ... // VI: ... // ES: ... - SPEC section refs in comments - Zod at module level with .safeParse() - (supabase as any) with eslint-disable - NextResponse.json() responses - Error format: { error: 'message' } ### Dependencies - CP-API-01: FAIL
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
1d ago
Completed
1d ago

Quality

Overall
100%
Pass
PASS
Graded By
-
Graded At
-

Cost

Input Tokens
-
Output Tokens
-
Est. USD
-

Result

{
  "cost": {
    "input_tokens": 379,
    "estimated_usd": 0.050907,
    "output_tokens": 3318
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 38399,
  "response_text": "I'll create the dashboard stats RPC API file that aggregates voter and team data according to the failing tests and acceptance criteria.\n\n```typescript\n// EN: Dashboard statistics API endpoints for voter and team data aggregation\n// VI: API endpoint thống kê dashboard cho tổng hợp dữ liệu cử tri và nhóm\n// ES: Endpoints de API de estadísticas del dashboard para agregación de datos de votantes y equipos\n// SPEC: CP-API-03 - Dashboard stats RPC (aggregate voter/team data)\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createClient } from '@supabase/supabase-js';\nimport { z } from 'zod';\n\n// EN: Dashboard stats request schema\n// VI: Schema yêu cầu thống kê dashboard\n// ES: Schema de solicitud de estadísticas del dashboard\nconst DashboardStatsRequestSchema = z.object({\n  timeframe: z.enum(['7d', '30d', '90d', '1y']).optional().default('30d'),\n  includeDetails: z.boolean().optional().default(false)\n});\n\n// EN: Recent activity request schema\n// VI: Schema yêu cầu hoạt động gần đây\n// ES: Schema de solicitud de actividad reciente\nconst RecentActivityRequestSchema = z.object({\n  limit: z.number().min(1).max(100).optional().default(10),\n  offset: z.number().min(0).optional().default(0)\n});\n\n// EN: Dashboard stats response type\n// VI: Kiểu phản hồi thống kê dashboard\n// ES: Tipo de respuesta de estadísticas del dashboard\ninterface DashboardStats {\n  voter_stats: {\n    total_voters: number;\n    active_voters: number;\n    sentiment_count: {\n      positive: number;\n      neutral: number;\n      negative: number;\n    };\n    engagement_rate: number;\n  };\n  team_stats: {\n    total_teams: number;\n    active_teams: number;\n    total_members: number;\n    average_team_size: number;\n  };\n  campaign_metrics: {\n    total_campaigns: number;\n    active_campaigns: number;\n    completion_rate: number;\n  };\n  timeframe: string;\n}\n\n// EN: Recent activity response type\n// VI: Kiểu phản hồi hoạt động gần đây\n// ES: Tipo de respuesta de actividad reciente\ninterface RecentActivity {\n  id: string;\n  type: 'voter_registration' | 'team_created' | 'campaign_started' | 'sentiment_recorded';\n  description: string;\n  timestamp: string;\n  user_id?: string;\n  metadata?: Record<string, any>;\n}\n\n// EN: Get dashboard statistics with voter and team aggregations\n// VI: Lấy thống kê dashboard với tổng hợp cử tri và nhóm\n// ES: Obtener estadísticas del dashboard con agregaciones de votantes y equipos\nexport async function getDashboardStats(request: NextRequest): Promise<NextResponse> {\n  try {\n    const url = new URL(request.url);\n    const timeframe = url.searchParams.get('timeframe') || '30d';\n    const includeDetails = url.searchParams.get('includeDetails') === 'true';\n\n    // EN: Validate request parameters\n    // VI: Xác thực tham số yêu cầu\n    // ES: Validar parámetros de solicitud\n    const validation = DashboardStatsRequestSchema.safeParse({\n      timeframe,\n      includeDetails\n    });\n\n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid request parameters' },\n        { status: 400 }\n      );\n    }\n\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;\n\n    if (!supabaseUrl || !supabaseKey) {\n      return NextResponse.json(\n        { error: 'Supabase configuration missing' },\n        { status: 500 }\n      );\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    // EN: Calculate date range based on timeframe\n    // VI: Tính toán phạm vi ngày dựa trên khung thời gian\n    // ES: Calcular rango de fechas basado en el marco temporal\n    const now = new Date();\n    const timeframeDays = {\n      '7d': 7,\n      '30d': 30,\n      '90d': 90,\n      '1y': 365\n    }[validation.data.timeframe];\n\n    const startDate = new Date(now.getTime() - (timeframeDays * 24 * 60 * 60 * 1000));\n\n    // EN: Aggregate voter statistics with sentiment analysis\n    // VI: Tổng hợp thống kê cử tri với phân tích cảm xúc\n    // ES: Agregar estadísticas de votantes con análisis de sentimientos\n    const { data: voterData, error: voterError } = await supabase\n      .rpc('get_voter_stats_with_sentiment', {\n        start_date: startDate.toISOString(),\n        end_date: now.toISOString()\n      });\n\n    if (voterError) {\n      console.error('Voter stats error:', voterError);\n      return NextResponse.json(\n        { error: 'Failed to fetch voter statistics' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Aggregate team statistics\n    // VI: Tổng hợp thống kê nhóm\n    // ES: Agregar estadísticas de equipos\n    const { data: teamData, error: teamError } = await supabase\n      .rpc('get_team_stats', {\n        start_date: startDate.toISOString(),\n        end_date: now.toISOString()\n      });\n\n    if (teamError) {\n      console.error('Team stats error:', teamError);\n      return NextResponse.json(\n        { error: 'Failed to fetch team statistics' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Aggregate campaign metrics\n    // VI: Tổng hợp số liệu chiến dịch\n    // ES: Agregar métricas de campañas\n    const { data: campaignData, error: campaignError } = await supabase\n      .rpc('get_campaign_metrics', {\n        start_date: startDate.toISOString(),\n        end_date: now.toISOString()\n      });\n\n    if (campaignError) {\n      console.error('Campaign stats error:', campaignError);\n      return NextResponse.json(\n        { error: 'Failed to fetch campaign metrics' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Compile dashboard statistics\n    // VI: Biên dịch thống kê dashboard\n    // ES: Compilar estadísticas del dashboard\n    const stats: DashboardStats = {\n      voter_stats: {\n        total_voters: voterData?.total_voters || 0,\n        active_voters: voterData?.active_voters || 0,\n        sentiment_count: {\n          positive: voterData?.sentiment_count?.positive || 0,\n          neutral: voterData?.sentiment_count?.neutral || 0,\n          negative: voterData?.sentiment_count?.negative || 0\n        },\n        engagement_rate: voterData?.engagement_rate || 0\n      },\n      team_stats: {\n        total_teams: teamData?.total_teams || 0,\n        active_teams: teamData?.active_teams || 0,\n        total_members: teamData?.total_members || 0,\n        average_team_size: teamData?.average_team_size || 0\n      },\n      campaign_metrics: {\n        total_campaigns: campaignData?.total_campaigns || 0,\n        active_campaigns: campaignData?.active_campaigns || 0,\n        completion_rate: campaignData?.completion_rate || 0\n      },\n      timeframe: validation.data.timeframe\n    };\n\n    return NextResponse.json(stats);\n\n  } catch (error) {\n    console.error('Dashboard stats error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get recent activity feed for dashboard\n// VI: Lấy nguồn cấp hoạt động gần đây cho dashboard\n// ES: Obtener feed de actividad reciente para el dashboard\nexport async function getRecentActivity(request: NextRequest): Promise<NextResponse> {\n  try {\n    const url = new URL(request.url);\n    const limit = parseInt(url.searchParams.get('limit') || '10');\n    const offset = parseInt(url.searchParams.get('offset') || '0');\n\n    // EN: Validate request parameters\n    // VI: Xác thực tham số yêu cầu\n    // ES: Validar parámetros de solicitud\n    const validation = RecentActivityRequestSchema.safeParse({\n      limit,\n      offset\n    });\n\n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid request parameters' },\n        { status: 400 }\n      );\n    }\n\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;\n\n    if (!supabaseUrl || !supabaseKey) {\n      return NextResponse.json(\n        { error: 'Supabase configuration missing' },\n        { status: 500 }\n      );\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    // EN: Fetch recent activity from various sources\n    // VI: Lấy hoạt động gần đây từ các nguồn khác nhau\n    // ES: Obtener actividad reciente de varias fuentes\n    const { data: recent_activity, error } = await supabase\n      .rpc('get_recent_activity_feed', {\n        limit_count: validation.data.limit,\n        offset_count: validation.data.offset\n      });\n\n    if (error) {\n      console.error('Recent activity error:', error);\n      return NextResponse.json(\n        { error: 'Failed to fetch recent activity' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      recent_activity: recent_activity || [],\n      pagination: {\n        limit: validation.data.limit,\n        offset: validation.data.offset,\n        has_more: (recent_activity?.length || 0) === validation.data.limit\n      }\n    });\n\n  } catch (error) {\n    console.error('Recent activity error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Main API handler for dashboard stats endpoints\n// VI: Xử lý API chính cho các endpoint thống kê dashboard\n// ES: Manejador de API principal para endpoints de estadísticas del dashboard\nexport async function GET(request: NextRequest): Promise<NextResponse> {\n  const url = new URL(request.url);\n  const endpoint = url.searchParams.get('endpoint');\n\n  switch (endpoint) {\n    case 'stats':\n      return getDashboardStats(request);\n    case 'activity':\n      return getRecentActivity(request);\n    default:\n      return NextResponse.json(\n        { error: 'Invalid endpoint' },\n        { status: 400 }\n      );\n  }\n}\n\nexport async function POST(request: NextRequest): Promise<NextResponse> {\n  return NextResponse.json(\n    { error: 'Method not allowed' },\n    { status: 405 }\n  );\n}\n```\n\nThis implementation:\n\n1. **Creates the required file** `campaignpro/src/api/stats.ts`\n\n2. **Includes voter stats with sentiment count** - The `getDashboardStats` function aggregates voter statistics including sentiment analysis with positive, neutral, and negative counts\n\n3. **Includes recent activity functionality** - The `getRecentActivity` function fetches and returns recent activity data for the dashboard\n\n4. **Follows codebase conventions**:\n   - Trilingual comments (EN/VI/ES)\n   - SPEC section reference in header\n   - Zod schemas with `.safeParse()`\n   - `(supabase as any)` with eslint-disable comment\n   - `NextResponse.json()` for responses\n   - Consistent error format `{ error: 'message' }`\n\n5. **Provides comprehensive dashboard statistics**:\n   - Voter statistics with sentiment analysis\n   - Team statistics and metrics\n   - Campaign performance metrics\n   - Recent activity feed with pagination\n\nThe file contains the required patterns for both sentiment/voter stats and recent activity functionality as specified in the failing tests."
}
Task ID: 9478937a-1b10-41b5-8399-98e5b5e6e99f