Skip to main content
The CGIAR Risk Intelligence Tool is built on a serverless AWS architecture with a multi-agent AI pipeline for automated risk assessment.

High-Level Architecture

Component Details

Frontend - Next.js 15 Static Export

Hosting: Deployed to S3 as static HTML/CSS/JS, served via CloudFront CDN with SPA fallback routing.
The frontend uses output: 'export' which requires all routes to be known at build time. Dynamic [id] segments are not supported - all entity IDs are passed via query parameters (e.g., /assessments/upload?id=uuid).

Backend - NestJS 10 REST API

Entry Points:
  • main.ts - Local development server (port 3001)
  • lambda.ts - AWS Lambda handler for API Gateway
  • worker.ts - Background job processor

Database - PostgreSQL 15 via Prisma

Schema Overview (see packages/api/prisma/schema.prisma):
VPC Configuration: The RDS instance resides in a private VPC with no public internet access. Only Lambda functions deployed in the same VPC can connect.
Migrations cannot run from local machines against the deployed RDS. Use the remote migration script:

Authentication - AWS Cognito

User Pool Configuration:
  • Email-based sign-in (username = email)
  • Password policy: 8+ chars, uppercase, lowercase, number, special char
  • MFA: Optional (disabled by default)
  • Admin group: admin (checked by AdminGuard in NestJS)
JWT Token Flow: Token Lifecycle:
  • Access Token: 60 minutes (short-lived for API requests)
  • Refresh Token: 30 days (if Remember Me checked) or session-only
  • Auto-Refresh: API client intercepts 401 responses, uses refresh token, retries request

AI Pipeline - AWS Bedrock Multi-Agent System

Model Configuration (see packages/shared/src/constants/bedrock.config.ts):
Agent Pipeline:
1

Parser Agent

Input: S3 URI to uploaded PDF/DOCXProcess:
  1. Fetch document from S3
  2. Extract text using AWS Textract (for PDFs) or raw text (DOCX)
  3. Send text to Bedrock with parser prompt
  4. Extract structured data across 35 risk indicators
Output: JSON object with extracted fields mapped to database schema
2

Gap Detector Agent

Input: Parsed data from Step 1 + expected field list (35 indicators)Process:
  1. Compare extracted data against required fields
  2. Validate data quality (completeness, format, confidence)
  3. Mark each field as VERIFIED, PARTIAL, or MISSING
Output: Array of GapField records inserted into database
3

Risk Analysis Agent

Input: All gap fields (extracted + corrected values)Process:
  1. For each of 7 categories:
    • Score 5 subcategories (0-100 scale)
    • Map to risk level: 0-33 Low, 34-66 Medium, 67-100 High
    • Generate evidence text citing data sources
  2. Calculate weighted average for category score
  3. Generate narrative explaining risk drivers
  4. Create 3-5 prioritized recommendations per category
Output: 7 RiskScore records + associated Recommendation records
4

Report Generator Agent

Input: Assessment ID (fetches all data from database)Process:
  1. Query all gap fields, risk scores, recommendations, comments
  2. Generate HTML report template with:
    • Executive summary
    • Company profile
    • Risk scorecard (7 categories + 35 subcategories)
    • Evidence and narratives
    • Prioritized recommendations
    • Appendices (methodology, data sources)
  3. Convert HTML to PDF using headless Chrome (Puppeteer in Lambda)
  4. Upload PDF to S3
  5. Generate pre-signed download URL (1-hour expiry)
Output: S3 URI + pre-signed URL
Agent Chaining: The job system automatically chains PARSE_DOCUMENTGAP_DETECTION. Analysts manually trigger RISK_ANALYSIS and REPORT_GENERATION after reviewing gaps.

Asynchronous Job System

Fire-and-Forget Pattern: Job Handler Interface (packages/api/src/jobs/job-handler.interface.ts):
Retry Logic:
  • Max attempts: 3 (configurable per job)
  • Backoff: Exponential (1s, 2s, 4s)
  • Permanent failure: Status set to FAILED, error message stored
  • Document status updated: PARSINGFAILED

Infrastructure - AWS CloudFormation

Stack Resources (infra/lib/alliance-risk-stack.ts):
Deployment Scripts:

Data Flow Examples

Example 1: Document Upload and Parsing

Example 2: Risk Analysis and Scoring

Performance Characteristics

Document Parsing

Typical: 30-60 seconds for 10-30 page PDFsVariables:
  • Document length (pages)
  • Text density
  • Textract processing time
  • Bedrock throttling
Timeout: 15 minutes (Worker Lambda)

Gap Detection

Typical: 10-20 secondsVariables:
  • Number of extracted fields
  • Validation complexity
Timeout: 15 minutes (Worker Lambda)

Risk Analysis

Typical: 60-90 seconds (7 categories × 10-15s each)Variables:
  • Bedrock API latency
  • Prompt complexity
  • Number of recommendations
Timeout: 15 minutes (Worker Lambda)

Report Generation

Typical: 20-30 secondsVariables:
  • Report length
  • PDF rendering complexity
  • S3 upload speed
Timeout: 15 minutes (Worker Lambda)
Cold Start Impact:
  • API Lambda: 1-2 seconds (arm64, 1024MB, bundled with esbuild)
  • Worker Lambda: 2-3 seconds (arm64, 1024MB, Prisma client generation)
  • Mitigation: Provisioned concurrency for production (not enabled in MVP)

Security Model

1

Authentication

  • AWS Cognito User Pool with email + password
  • JWT tokens (access: 60min, refresh: 30 days)
  • Token rotation on refresh
  • Rate limiting: 5 req/min on auth endpoints (NestJS Throttler)
2

Authorization

  • Global JwtAuthGuard on all API routes (except @Public())
  • JWT signature verification against Cognito JWKS
  • User roles via Cognito groups (admin group)
  • AdminGuard checks cognito:groups claim
  • Resource ownership validation (userId must match createdById)
3

Data Encryption

  • In Transit: HTTPS only (CloudFront, API Gateway, S3 pre-signed URLs)
  • At Rest: RDS encrypted with AWS KMS, S3 SSE-AES256
  • Secrets: Database credentials in Secrets Manager, auto-rotation enabled
4

Network Isolation

  • RDS in private VPC subnets (no internet gateway)
  • Lambda functions in VPC to access RDS
  • VPC endpoint for Cognito (interface endpoint)
  • S3 and Bedrock accessed via NAT Gateway (or VPC endpoints in production)
5

Input Validation

  • NestJS ValidationPipe with class-validator on all DTOs
  • File upload limits: 10MB max, PDF/DOCX only (MIME type validation)
  • SQL injection prevention: Prisma ORM (parameterized queries)
  • XSS prevention: React auto-escaping, CSP headers on CloudFront
Production Hardening Checklist (not implemented in MVP):
  • Enable CloudTrail for API audit logs
  • Add WAF rules to CloudFront (rate limiting, geo-blocking)
  • Implement RBAC beyond admin/analyst (e.g., viewer, editor roles)
  • Add MFA enforcement for admin accounts
  • Enable VPC Flow Logs
  • Set up CloudWatch alarms for error rates, latency, failed auth

Monitoring and Observability

CloudWatch Logs:
  • /aws/lambda/alliance-risk-api - API request/response, errors
  • /aws/lambda/alliance-risk-worker - Job processing, Bedrock calls, failures
  • /aws/rds/cluster/alliance-risk/postgresql - Slow queries, errors
Key Metrics:
  • Lambda invocations, duration, errors, throttles
  • RDS connections, CPU, memory, storage
  • Bedrock API latency, throttling, errors
  • S3 request counts, error rates
Cost Breakdown (estimated monthly for dev environment):
  • RDS db.t3.micro: $15
  • Lambda (API + Worker): $5-10 (low traffic)
  • Bedrock (Claude 3.5 Sonnet): $20-50 (100-200 assessments/month)
  • S3: $2
  • CloudFront: $5
  • Total: ~$50-80/month
Use AWS Cost Explorer to track Bedrock usage. Each assessment consumes approximately:
  • Parsing: 50K input tokens + 5K output tokens
  • Gap Detection: 10K input + 2K output
  • Risk Analysis: 30K input + 10K output (×7 categories)
  • Report Generation: 20K input + 15K output
Total per assessment: ~200K tokens = ~$0.30 at Claude 3.5 Sonnet v2 pricing

Development Workflow

Scalability Considerations

Current Limits (MVP configuration):
  • RDS: 100 concurrent connections (db.t3.micro)
  • Lambda: 10 concurrent executions (soft limit, can increase)
  • Bedrock: 5 requests/second (default throttle per model)
  • S3: 5,500 GET/3,500 PUT per second per prefix (effectively unlimited)
Scaling Strategies (for production):
  • RDS: Upgrade to db.r6g.xlarge for 100+ concurrent users
  • Lambda: Increase reserved concurrency for API and Worker
  • Bedrock: Request quota increase (up to 1000 req/s per model)
  • CloudFront: No action needed, auto-scales globally
  • Database: Add indexes on assessmentId, userId, status columns
  • Caching: Add Redis/ElastiCache for session storage, prompt caching
  • CDN: Cache API responses for read-heavy endpoints (GET /api/prompts/section/:section)
  • Bedrock: Batch subcategory scoring (1 call instead of 5 per category)
  • Lambda: Use ARM64 (20% cheaper, already implemented)
  • RDS: Enable auto-pause for dev/staging (Aurora Serverless v2)
  • S3: Lifecycle policy to Glacier after 90 days
  • Bedrock: Switch to Claude 3 Haiku for parsing (5x cheaper, acceptable accuracy)