> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/AllianceBioversityCIAT/alliance-risk-analysis-tool/llms.txt
> Use this file to discover all available pages before exploring further.

# PDF Report Generation

> Generate comprehensive risk assessment reports with traffic-light indicators, radar charts, and actionable recommendations

## Overview

The CGIAR Risk Intelligence Tool generates professional PDF reports that synthesize risk analysis results into executive-ready documents. Reports include traffic-light risk indicators, visualization-ready data, and prioritized recommendations.

## Report Structure

Every report contains:

<Steps>
  <Step title="Executive Summary">
    High-level overview of the assessment with overall risk score and level
  </Step>

  <Step title="Overall Risk Profile">
    Aggregate score (0-100) and traffic-light level (LOW/MODERATE/HIGH/CRITICAL)
  </Step>

  <Step title="Category Breakdown">
    Detailed analysis for all 7 risk categories with subcategory scores
  </Step>

  <Step title="Radar Chart Data">
    Visualization-ready data for multi-dimensional risk radar chart
  </Step>

  <Step title="Evidence & Narratives">
    AI-generated risk narratives with supporting evidence from documents
  </Step>

  <Step title="Recommendations">
    Prioritized action items (HIGH/MEDIUM/LOW) for each risk category
  </Step>
</Steps>

## Generating a Report

### 1. Retrieve Report Data

First, fetch the complete report structure:

<CodeGroup>
  ```typescript GET /api/assessments/:id/report theme={null}
  ```

  ```typescript Response (ReportResponse) theme={null}
  {
    "assessment": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Q1 2026 Partnership Assessment",
      "companyName": "Green Valley Agritech",
      "companyType": "Agricultural Cooperative",
      "country": "Kenya",
      "status": "COMPLETE",
      "intakeMode": "UPLOAD",
      "progress": 100,
      "version": 8,
      "overallRiskScore": 42,
      "overallRiskLevel": "MODERATE",
      "createdAt": "2026-03-04T10:30:00Z",
      "updatedAt": "2026-03-04T14:45:00Z"
    },
    "executiveSummary": "Risk assessment for Green Valley Agritech has been completed with an overall risk score of 42. The assessment identifies moderate risks in financial and operational areas, with particular attention needed for revenue stability and supply chain resilience.",
    "overallScore": 42,
    "overallLevel": "MODERATE",
    "categories": [
      {
        "id": "score-financial",
        "category": "FINANCIAL",
        "score": 38,
        "level": "MODERATE",
        "subcategories": [
          {
            "name": "Revenue Stability",
            "indicator": "Year-over-year revenue variance",
            "score": 42,
            "level": "MODERATE",
            "evidence": "Revenue declined 8% in FY2025 but stabilized in Q4. Customer base concentrated in 3 major clients.",
            "mitigation": "Diversify customer base and develop new revenue streams in adjacent markets."
          },
          // ... 4 more subcategories
        ],
        "evidence": "Financial statements show moderate volatility with adequate liquidity. Operating margin below industry average.",
        "narrative": "The Financial risk level is MODERATE based on analysis of revenue trends, profitability metrics, debt levels, cash flow patterns, and financial sustainability indicators. While the organization maintains adequate liquidity, revenue concentration and below-average margins present manageable risks.",
        "recommendations": [
          {
            "id": "rec-financial-1",
            "text": "Develop a 3-year financial sustainability plan focusing on revenue diversification and cost optimization.",
            "priority": "HIGH",
            "isEdited": false,
            "editedText": null
          },
          {
            "id": "rec-financial-2",
            "text": "Implement quarterly financial health monitoring with early warning indicators for revenue and margin trends.",
            "priority": "MEDIUM",
            "isEdited": false,
            "editedText": null
          }
        ]
      },
      // ... 6 more categories
    ],
    "radarData": [
      { "category": "FINANCIAL", "score": 38 },
      { "category": "OPERATIONAL", "score": 45 },
      { "category": "MARKET", "score": 52 },
      { "category": "BEHAVIORAL", "score": 28 },
      { "category": "CLIMATE_ENVIRONMENTAL", "score": 61 },
      { "category": "GOVERNANCE_LEGAL", "score": 33 },
      { "category": "TECHNOLOGY_DATA", "score": 48 }
    ]
  }
  ```
</CodeGroup>

<Note>
  Reports are only available for assessments with status `COMPLETE`. Attempting to generate a report for an incomplete assessment will return a 400 error.
</Note>

### 2. Generate PDF

Trigger PDF generation as a background job:

<CodeGroup>
  ```typescript POST /api/assessments/:id/report/pdf theme={null}
  ```

  ```typescript Response theme={null}
  {
    "jobId": "job-pdf-550e8400"
  }
  ```
</CodeGroup>

### 3. Poll Job Status

Monitor PDF generation progress:

<CodeGroup>
  ```typescript GET /api/jobs/:jobId theme={null}
  ```

  ```typescript Response (PROCESSING) theme={null}
  {
    "id": "job-pdf-550e8400",
    "type": "REPORT_GENERATION",
    "status": "PROCESSING",
    "input": { "assessmentId": "550e8400-e29b-41d4-a716-446655440000" },
    "result": null,
    "attempts": 1,
    "createdAt": "2026-03-04T14:50:00Z",
    "startedAt": "2026-03-04T14:50:02Z"
  }
  ```

  ```typescript Response (COMPLETED) theme={null}
  {
    "id": "job-pdf-550e8400",
    "type": "REPORT_GENERATION",
    "status": "COMPLETED",
    "input": { "assessmentId": "550e8400-e29b-41d4-a716-446655440000" },
    "result": {
      "assessmentId": "550e8400-e29b-41d4-a716-446655440000",
      "pdfKey": "assessments/550e8400/reports/report-1709563800.pdf",
      "downloadUrl": "https://s3.amazonaws.com/bucket/assessments/550e8400/reports/report-1709563800.pdf?..."
    },
    "attempts": 1,
    "createdAt": "2026-03-04T14:50:00Z",
    "startedAt": "2026-03-04T14:50:02Z",
    "completedAt": "2026-03-04T14:50:15Z"
  }
  ```
</CodeGroup>

### 4. Download PDF

Use the presigned URL from the job result:

```typescript theme={null}
const { result } = job;
window.location.href = result.downloadUrl;
```

<Info>
  Presigned URLs are valid for 1 hour. If the URL expires, regenerate the PDF or request a new presigned URL.
</Info>

## Traffic-Light Indicators

Reports use color-coded risk levels for quick visual assessment:

<Tabs>
  <Tab title="🟢 LOW (0-24)">
    **Visual Representation:**

    * Background: Light green (#F0FDF4)
    * Text: Dark green (#166534)
    * Icon: 🟢 Green circle

    **Interpretation:** Minimal risk. Standard monitoring sufficient.
  </Tab>

  <Tab title="🟡 MODERATE (25-49)">
    **Visual Representation:**

    * Background: Light yellow (#FEFCE8)
    * Text: Dark yellow (#854D0E)
    * Icon: 🟡 Yellow circle

    **Interpretation:** Acceptable risk. Enhanced oversight recommended.
  </Tab>

  <Tab title="🟠 HIGH (50-74)">
    **Visual Representation:**

    * Background: Light orange (#FFF7ED)
    * Text: Dark orange (#9A3412)
    * Icon: 🟠 Orange circle

    **Interpretation:** Significant risk. Active mitigation required.
  </Tab>

  <Tab title="🔴 CRITICAL (75-100)">
    **Visual Representation:**

    * Background: Light red (#FEF2F2)
    * Text: Dark red (#991B1B)
    * Icon: 🔴 Red circle

    **Interpretation:** Unacceptable risk. Immediate intervention required.
  </Tab>
</Tabs>

## Radar Chart Visualization

The `radarData` array enables multi-dimensional risk visualization:

<CodeGroup>
  ```typescript React Component (Recharts) theme={null}
  import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis } from 'recharts';
  import type { ReportResponse } from '@alliance-risk/shared';

  export function RiskRadarChart({ report }: { report: ReportResponse }) {
    return (
      <RadarChart
        width={600}
        height={600}
        data={report.radarData}
        margin={{ top: 20, right: 30, bottom: 20, left: 30 }}
      >
        <PolarGrid stroke="#e5e7eb" />
        <PolarAngleAxis
          dataKey="category"
          tick={{ fill: '#6b7280', fontSize: 12 }}
        />
        <PolarRadiusAxis
          domain={[0, 100]}
          tick={{ fill: '#6b7280' }}
          tickCount={5}
        />
        <Radar
          name="Risk Score"
          dataKey="score"
          stroke="#4f46e5"
          fill="#4f46e5"
          fillOpacity={0.5}
        />
      </RadarChart>
    );
  }
  ```

  ```typescript Data Structure theme={null}
  radarData: [
    { category: 'FINANCIAL', score: 38 },
    { category: 'OPERATIONAL', score: 45 },
    { category: 'MARKET', score: 52 },
    { category: 'BEHAVIORAL', score: 28 },
    { category: 'CLIMATE_ENVIRONMENTAL', score: 61 },
    { category: 'GOVERNANCE_LEGAL', score: 33 },
    { category: 'TECHNOLOGY_DATA', score: 48 }
  ]
  ```
</CodeGroup>

## Report Sections

### Executive Summary

AI-generated high-level summary:

```typescript theme={null}
executiveSummary: "Risk assessment for Green Valley Agritech has been completed with an overall risk score of 42. The assessment identifies moderate risks in financial and operational areas, with particular attention needed for revenue stability and supply chain resilience."
```

<Info>
  The executive summary is dynamically generated based on the overall risk level and highest-scoring categories.
</Info>

### Category Details

Each category includes:

<AccordionGroup>
  <Accordion title="Subcategory Scores" icon="list">
    Array of 5 subcategories with individual scores, evidence, and mitigation strategies:

    ```typescript theme={null}
    {
      "name": "Revenue Stability",
      "indicator": "Year-over-year revenue variance",
      "score": 42,
      "level": "MODERATE",
      "evidence": "Revenue declined 8% in FY2025 but stabilized in Q4",
      "mitigation": "Diversify customer base and develop new revenue streams"
    }
    ```
  </Accordion>

  <Accordion title="Evidence Summary" icon="file-text">
    Consolidated evidence from document parsing and data analysis:

    ```typescript theme={null}
    evidence: "Financial statements show moderate volatility with adequate liquidity. Operating margin below industry average."
    ```
  </Accordion>

  <Accordion title="Risk Narrative" icon="book">
    AI-generated contextual explanation:

    ```typescript theme={null}
    narrative: "The Financial risk level is MODERATE based on analysis of revenue trends, profitability metrics, debt levels, cash flow patterns, and financial sustainability indicators."
    ```
  </Accordion>

  <Accordion title="Recommendations" icon="lightbulb">
    Prioritized action items with HIGH/MEDIUM/LOW urgency:

    ```typescript theme={null}
    recommendations: [
      {
        "text": "Develop a 3-year financial sustainability plan...",
        "priority": "HIGH",
        "isEdited": false,
        "editedText": null
      }
    ]
    ```
  </Accordion>
</AccordionGroup>

## PDF Generation Process

The report generation handler orchestrates PDF creation:

<Steps>
  <Step title="Validate Assessment">
    Ensure assessment is in `COMPLETE` status with risk scores available
  </Step>

  <Step title="Fetch Report Data">
    Retrieve complete report response from `ReportService.getReport()`
  </Step>

  <Step title="Generate PDF">
    Use `PdfService.generate()` to create PDF buffer from report data

    **Current Implementation:** Simple text-based stub

    **Future Enhancement:** Use Puppeteer or PDFKit for rich formatting
  </Step>

  <Step title="Upload to S3">
    Store PDF in S3 at key: `assessments/{assessmentId}/reports/report-{timestamp}.pdf`
  </Step>

  <Step title="Generate Presigned URL">
    Create temporary download URL (1-hour expiration)
  </Step>

  <Step title="Update Assessment">
    Set `progress` to 100%
  </Step>
</Steps>

<CodeGroup>
  ```typescript ReportGenerationHandler.execute theme={null}
  async execute(input: ReportGenerationInput): Promise<ReportGenerationResult> {
    const assessment = await this.prisma.assessment.findUnique({
      where: { id: input.assessmentId },
    });

    if (!assessment) {
      throw new Error(`Assessment ${input.assessmentId} not found`);
    }

    const reportId = `report-${Date.now()}`;
    const pdfKey = this.storageService.buildReportKey(
      input.assessmentId,
      reportId
    );

    // In production: Generate actual PDF with PdfService
    // const pdfBuffer = await this.pdfService.generate(reportData);
    // await this.storageService.upload(pdfKey, pdfBuffer);

    const downloadUrl = await this.storageService.generatePresignedDownloadUrl(pdfKey);

    await this.prisma.assessment.update({
      where: { id: input.assessmentId },
      data: { progress: 100 },
    });

    return { assessmentId: input.assessmentId, pdfKey, downloadUrl };
  }
  ```

  ```typescript S3 Key Convention theme={null}
  // StorageService.buildReportKey
  buildReportKey(assessmentId: string, reportId: string): string {
    return `assessments/${assessmentId}/reports/${reportId}.pdf`;
  }

  // Example:
  // "assessments/550e8400-e29b-41d4-a716-446655440000/reports/report-1709563800.pdf"
  ```
</CodeGroup>

## React Hook: Report Generation

```typescript hooks/use-report.ts theme={null}
import { useState } from 'react';
import { useJob } from './use-job';
import { apiClient } from '@/lib/api-client';

export function useReport(assessmentId: string) {
  const [jobId, setJobId] = useState<string | null>(null);
  const { job, isLoading } = useJob(jobId, { pollInterval: 3000 });

  const generatePdf = async () => {
    const response = await apiClient.post(
      `/api/assessments/${assessmentId}/report/pdf`
    );
    setJobId(response.jobId);
  };

  const downloadUrl = job?.status === 'COMPLETED' 
    ? job.result?.downloadUrl 
    : null;

  return {
    generatePdf,
    isGenerating: isLoading,
    downloadUrl,
    error: job?.status === 'FAILED' ? job.error : null,
  };
}
```

## Code Example: Complete Report Flow

```typescript theme={null}
import { useReport } from '@/hooks/use-report';
import { Button } from '@/components/ui/button';

function ReportActions({ assessmentId }: { assessmentId: string }) {
  const { generatePdf, isGenerating, downloadUrl, error } = useReport(assessmentId);

  return (
    <div className="space-y-4">
      <Button
        onClick={generatePdf}
        disabled={isGenerating}
      >
        {isGenerating ? 'Generating PDF...' : 'Generate PDF Report'}
      </Button>

      {downloadUrl && (
        <Button
          variant="outline"
          onClick={() => window.location.href = downloadUrl}
        >
          Download Report
        </Button>
      )}

      {error && (
        <div className="text-red-600">
          Failed to generate report: {error}
        </div>
      )}
    </div>
  );
}
```

## Future Enhancements

<CardGroup cols={2}>
  <Card title="Rich PDF Formatting" icon="paintbrush">
    Replace text-based stub with Puppeteer or PDFKit for professional layouts, charts, and branding
  </Card>

  <Card title="Custom Templates" icon="file-lines">
    Allow organizations to customize report templates with logos, colors, and section ordering
  </Card>

  <Card title="Multi-Language Support" icon="language">
    Generate reports in multiple languages based on assessment country or user preference
  </Card>

  <Card title="Report Versioning" icon="code-branch">
    Track report versions and allow regeneration with updated scores after data corrections
  </Card>
</CardGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Validate Completion" icon="check-circle">
    Always check assessment status is `COMPLETE` before generating reports
  </Card>

  <Card title="Cache Report Data" icon="database">
    Cache the report response to avoid re-fetching when generating multiple PDFs
  </Card>

  <Card title="Handle Expired URLs" icon="clock">
    Presigned URLs expire after 1 hour. Regenerate if user returns later to download
  </Card>

  <Card title="Monitor Job Failures" icon="bell">
    Implement error tracking for failed PDF generation jobs to identify systemic issues
  </Card>
</CardGroup>

## Related Resources

<CardGroup cols={2}>
  <Card title="Risk Scoring" href="/features/risk-scoring" icon="chart-line">
    Understand the scoring methodology behind report data
  </Card>

  <Card title="AI Analysis" href="/features/ai-analysis" icon="brain">
    Learn how AI agents generate narratives and recommendations
  </Card>

  <Card title="Assessment Workflow" href="/features/assessments" icon="list-check">
    See the complete assessment lifecycle from creation to report
  </Card>
</CardGroup>
