Skip to Content
ReportsReports — Architecture

Reports — Architecture

Data Model

Reports from the agent source are Activity rows (deliverableType=monthly_report, status=done). DM-uploaded and AI-generated reports live in the Report table (packages/db/prisma/schema.prisma).

Report table — key fields

FieldTypeNotes
idStringCUID
tenantIdStringFK to Tenant
labelStringDisplay name
periodStringe.g. “March 2026”
startDate / endDateDateTimeReporting window
sourceString"dm_upload" | "ai_generated" | "scheduled"
frequencyString?"weekly" | "monthly" — populated when source="scheduled"
statusString"generating" | "ready" | "failed"
contentTypeString"markdown" | "pdf"
contentString?Markdown body (ai_generated / markdown uploads)
fileUrlString?DigitalOcean Spaces URL (pdf uploads)
spacesKey / spacesbucketString?DO Spaces coordinates
userPromptString?Original natural-language prompt (ai_generated only)
requestedByName / requestedByIdString?Who triggered AI generation
uploadedByName / uploadedByIdString?Who uploaded (dm_upload only)

AI Generation Flow (ai_generated source)

User submits prompt + date range POST /api/reports/generate ← Dashboard API route POST /dm/v1/reports/generate ← DM portal proxy → Fastify API Create Report(status="generating") in DB enqueueCustomReportWriter() → BullMQ queue: agent__custom-report-writer custom-report-writer.worker.ts 1. Load client context (context-file-writer output) 2. Load recent channel insights 3. Load goals (targets + actuals) 4. RAG search for relevant knowledge base content 5. Call Claude (claude-sonnet-4-6, 300s timeout) 6. Save markdown → Report.content, set status="ready" 7. Enqueue email notification to requester

Queue type

CustomReportWriterJobData in packages/queue/src/types.ts

Enqueue helper

enqueueCustomReportWriter() exported from @leadmetrics/queue

Worker registration

apps/servers/agents/src/index.ts


Monthly Report Agent Flow (agent source)

The activity planner creates a monthly_report activity as part of the standard deliverable plan. When the activity’s status reaches done, it appears on the Reports list pages automatically — no separate Report row is created; the Activity record is queried directly.


API Endpoints

MethodPathAppNotes
GET/dm/v1/reports?tenantId=Fastify APIMerged list of all sources, sorted by createdAt desc
GET/dm/v1/reports/:id?tenantId=Fastify APISingle report detail
POST/dm/v1/reportsFastify APIUpload markdown or PDF (dm_upload source)
POST/dm/v1/reports/generateFastify APIStart AI generation (ai_generated source)
POST/api/reports/generateDashboard Next.js routeDashboard-side AI generation trigger (uses JWT session cookie)

Dashboard list and detail pages query Prisma directly (server components) — they do not go through the Fastify API.


File Locations

Dashboard

FilePurpose
apps/dashboard/src/app/(dashboard)/reports/page.tsxServer component — queries DB, passes data to client
apps/dashboard/src/app/(dashboard)/reports/ReportsClient.tsxList UI
apps/dashboard/src/app/(dashboard)/reports/detail/[id]/page.tsxServer component — fetches single report
apps/dashboard/src/app/(dashboard)/reports/detail/[id]/ReportDetailClient.tsxDetail UI + PDF download
apps/dashboard/src/components/reports/GenerateReportModal.tsxPrompt + date range modal
apps/dashboard/src/app/api/reports/generate/route.tsNext.js API route for AI generation

DM Portal

FilePurpose
apps/dm/src/app/(dm)/reports/page.tsxServer component
apps/dm/src/app/(dm)/reports/DmReportsClient.tsxList UI
apps/dm/src/app/(dm)/reports/[id]/page.tsxServer component
apps/dm/src/app/(dm)/reports/[id]/ReportDetailClient.tsxDetail UI + PDF download
apps/dm/src/components/reports/GenerateReportModal.tsxAI generation modal
apps/dm/src/components/reports/UploadReportModal.tsxPDF/Markdown upload modal
apps/dm/src/app/api/dm/reports/route.tsProxy → Fastify (GET list, POST upload)
apps/dm/src/app/api/dm/reports/generate/route.tsProxy → Fastify (POST generate)

Backend

FilePurpose
packages/agents/src/workers/custom-report-writer.worker.tsBullMQ worker — Claude call + DB save
apps/api/src/routers/dm/reports.tsFastify router — all /dm/v1/reports routes
packages/queue/src/types.tsCustomReportWriterJobData type
packages/queue/src/queues.tsenqueueCustomReportWriter() helper


Scheduled Performance Reports (source="scheduled")

Weekly and monthly reports generated automatically for all active tenants by the reporting server.

Schedule

FrequencyTriggerPeriod covered
WeeklyFriday 18:00–18:30 tenant local timeMonday – Friday of the current week
MonthlyLast day of month 18:00–18:30 tenant local time1st – last day of the month

Flow

Reporting server cron (every 30 min) → checks Friday / last-day-of-month + 18:00 local window → skip if tenant has no channel insights at all → skip if report for this period already exists (dedupeKey check) → create Report(source="scheduled", frequency, status="generating") → enqueuePerformanceReportWriter() Agents server — performance-report-writer.worker.ts → db.agentConfig.findUnique({ role: "performance-report-writer" }) → adapter, model, systemPrompt (editable from Manage portal) → load data in parallel: - ClientContext.content - ChannelInsight (current period, last 12, status=done) - Goal (last 10) - Previous Report of same frequency (for comparison; falls back to most recent channel insights if no prior report exists) → createSkillsDir(tenantId, "performance-report-writer") → skills → getAdapter(adapter).execute(prompt, config, skillsDir) → save Report.content, status="ready" → create Notification(tenantId, type="performance_report_ready") → enqueueNotification email → all TenantMembers + all DM users

Guard

Tenants with zero ChannelInsight records (no integrations connected) are skipped entirely. The report will not be generated until at least one insight worker has run.

DedupeKey format

FrequencyKey
Weeklyperf_weekly__{tenantId}__{YYYY-Www}
Monthlyperf_monthly__{tenantId}__{YYYY-MM}

AgentConfig

role: "performance-report-writer" — seeded in packages/db/src/seed.ts. Adapter, model, and system prompt are all editable from the Manage portal → Agents → Performance Report Writer.

Report structure (16 sections)

Cover block · Executive Summary (KPI table + highlight cards) · Goal Progress · Organic Traffic · Organic Conversions · AI Search Visibility · Google Ads · Meta Ads · Google Business Profile · SEO Performance · Content Performance · Social Media · Email Marketing · Key Wins · Issues & Risks · Next Period Priorities

Notification recipients

ChannelRecipients
In-app bellAll TenantMembers via Notification DB record (dashboard + DM portal)
EmailAll TenantMembers (dashboard users) + all users with role: super_admin or reviewer (DM team)

Email template slug: performance_report_ready — lightweight HTML with View Full Report CTA.

File locations

FilePurpose
apps/servers/reporting/src/jobs/performance-report.tsCron job — creates Report records + enqueues
apps/servers/reporting/src/scheduler.tsAdds weekly + monthly cron ticks
packages/agents/src/workers/performance-report-writer.worker.tsWorker — adapter/skills/AgentConfig pattern
packages/queue/src/types.tsPerformanceReportWriterJobData
packages/queue/src/queues.tsenqueuePerformanceReportWriter()

Agent Chat Integration

The generateReport tool (packages/ai-chat/src/tools/analytics/generate-report.ts) lets users request reports from the AI Chat at /chat. The LangGraph analytics agent calls it when the user asks for a report, analysis, or performance summary. The tool creates the Report record, enqueues the BullMQ job, and returns a viewUrl link in the chat response.

© 2026 Leadmetrics — Internal use only