Skip to content

feat(reports): PDF report generator (auth-gated download, pdfkit, real schema)#8

Open
kiro-agent[bot] wants to merge 1 commit into
mainfrom
feat/pdf-report-generator
Open

feat(reports): PDF report generator (auth-gated download, pdfkit, real schema)#8
kiro-agent[bot] wants to merge 1 commit into
mainfrom
feat/pdf-report-generator

Conversation

@kiro-agent

@kiro-agent kiro-agent Bot commented May 29, 2026

Copy link
Copy Markdown

This pull request was created by @kiro-agent on behalf of @D-source1602 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro autonomous agent


Summary

Real PDF report generator wired end-to-end. Replaces the mock setTimeout "GENERATE REPORT" button with a backend-backed PDF that you can actually download.

What it does

Action Result
Click GENERATE REPORT on the Reports page Backend pulls emission logs (and AQI if PR #7 is also merged) for the selected period, renders a PDF with pdfkit, persists a Report row, returns metadata
Click DOWNLOAD on a report card Authenticated fetch → Blob → triggers a browser download. The PDF is served streamed from backend/files/reports/<uuid>.pdf
Click DEL on a report card DB row removed + file deleted
Refresh the page Real reports load from /api/v1/reports (scoped: INDUSTRY users see only their own)

Architecture

                     [ Reports page UI ]
                              |
                              | POST /api/v1/reports/generate { type, periodStart, periodEnd }
                              v
                  [ requireAuth (PR #5) middleware ]
                              |
                              v
                  +-----------------------+
                  | report.controller     |  validate (zod)
                  +-----------+-----------+  scope INDUSTRY -> own industry
                              |
                              v
                  +-----------------------+
                  | reportService         |  pull EmissionLog + (optionally) AQI
                  | renderPdf()           |  pdfkit -> backend/files/reports/X.pdf
                  | prisma.report.create  |  persist metadata
                  +-----------+-----------+
                              |
                              v
                  +-----------------------+
                  | { downloadUrl, ... }  |
                  +-----------------------+

The PDF layout has a brand bar, dashed separator, KPI grid (records / totals / status counts color-coded green/amber/red), an emissions table, an AQI section (auto-skipped if PR #7 isn't merged), and a footer strapline. Built-in Helvetica only — no font shipping.

Why pdfkit over Puppeteer

  • pdfkit: ~2 MB, pure JS, no Chromium download
  • Puppeteer: ~300 MB Chromium download, slow cold-start
  • Same visual quality for our internal-style report
  • Easy to swap later if we need full HTML/CSS rendering for marketing-grade PDFs

Endpoints (all behind requireAuth)

Method Path Notes
POST /api/v1/reports/generate INDUSTRY users always scoped to their own factory
GET /api/v1/reports Industry → own; Admin/Inspector → all
GET /api/v1/reports/:id/download Streams the PDF; ownership-checked
DELETE /api/v1/reports/:id DB row + file cleanup

Schema additions

enum ReportType   { EMISSIONS_MONTHLY EMISSIONS_QUARTERLY COMPLIANCE_QUARTERLY ANNUAL_REPORT FACILITY_AUDIT AI_INSIGHTS CUSTOM }
enum ReportFormat { PDF CSV }

model Report {
  id            String       @id @default(cuid())
  type          ReportType
  format        ReportFormat @default(PDF)
  title         String
  periodStart   DateTime?
  periodEnd     DateTime?
  storageKey    String       // relative path under backend/files/
  sizeBytes     Int
  pageCount     Int?
  generatedById String?
  generatedBy   User?     @relation("ReportsGenerated", ...)
  industryId    String?
  industry      Industry? @relation(...)
  createdAt     DateTime  @default(now())

  @@index([createdAt(sort: Desc)])
  @@index([industryId, createdAt(sort: Desc)])
  @@map("reports")
}

Independence from PR #7 (realtime AQI)

Either merge order works. The AQI section of the PDF appears only once both PR #7 and this PR are merged — reportService.ts does a runtime probe on prisma.airQualityReading and silently skips the section if the model isn't generated. No code-level dependency between the two branches.

To run after merging

cd backend
npm install                                         # picks up pdfkit
npx prisma migrate dev --name add_reports           # creates `reports` table
# JWT secrets and DATABASE_URL must already be set from earlier PRs.
npm run dev

# in another terminal
cd CLIMATRIX
npm run dev                                         # no new frontend deps

Open http://localhost:5173, log in, navigate to the Reports tab. Pick a Report Type + Period, click GENERATE REPORT. The new card appears in the archive list — click DOWNLOAD and the PDF should land in your Downloads folder.

If backend/files/ doesn't exist yet, the service creates it on first import.

Diff stat

9 files, +1065 / -21.

…nloadable)

BACKEND
- New Report model + ReportType / ReportFormat enums; back-relations on
  User and Industry. Indexed on (createdAt DESC), (industryId, createdAt DESC).
- services/reportService.ts: pdfkit-based generation, ~2 MB dependency
  (vs Puppeteer's ~300 MB). Brand palette + dashed separators match the
  frontend's sketch-on-paper aesthetic. KPI grid + striped data tables
  with status color-coding (green/amber/red). Auto-paginates long lists.
- AQI section is gated behind a runtime model probe — this PR is
  independent of PR #7. Once #7 is merged, reports auto-include an AQI
  summary; otherwise the section is silently skipped.
- POST /api/v1/reports/generate    create + persist report + write PDF
- GET  /api/v1/reports             list (scoped: INDUSTRY -> own only)
- GET  /api/v1/reports/:id/download   stream PDF with auth + ownership
- DELETE /api/v1/reports/:id       remove DB row + best-effort file
- All routes auth-protected via requireAuth middleware (PR #5).
- Files stored under backend/files/reports/<uuid>.pdf, .gitignored.
- New deps: pdfkit, @types/pdfkit.

FRONTEND
- src/lib/reports.ts: typed list / generate / delete / download helpers,
  uiTypeToBackend mapper for the existing UI label strings, formatBytes.
- App.tsx Reports component:
    -> Loads real reports from /api/v1/reports on mount (with loading state)
    -> 'GENERATE REPORT' button now hits backend; period selector mapped
       to ISO date range (Current Month, Last Month, Q1/Q2 2024, Full Year)
    -> Report cards show real metadata (title, generatedAt, sizeBytes,
       pageCount, industry)
    -> 'DOWNLOAD' button per row uses authenticated fetch + Blob trigger
       (browsers can't send Authorization on plain anchor downloads)
    -> Inline error band for both load and generate failures
    -> Removed the localStorage S.get/S.set scaffolding for reports

Migration required after pulling:
  cd backend && npm install
  npx prisma migrate dev --name add_reports

Independent of PR #7 (realtime AQI). Either merge order works. AQI section
of the PDF appears once both this PR and #7 are merged.
D-source1602 added a commit that referenced this pull request May 29, 2026
…ased

feat(reports): PDF report generator (rebased on main, conflict-free) — supersedes #8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant