Skip to main content

Media & Reports Module

The Media & Reports module is a comprehensive content management and document generation system for the Comdeall platform that handles file storage, media management, and automated report generation. It integrates with DigitalOcean Spaces (S3-compatible) for cloud storage and uses Puppeteer for PDF generation, providing a complete solution for media uploads, downloads, cleanup, and assessment report creation across multiple assessment types.

Table of Contents

  1. Module Structure
  2. Media Endpoints
  3. Core Features
  4. Media Management
  5. Report Generation System
  6. Assessment Report Types
  7. CDDC Scattering Logic
  8. Storage Architecture
  9. Security & Performance
  10. Technical Implementation

Module Structure

@Module({
imports: [DBModule],
controllers: [MediaController],
providers: [
MediaService,
ReportService,
{
provide: 'StorageProvider',
useClass: DigitalOceanProvider,
},
],
})
export class MediaModule {}

Architecture Components

  • MediaController - REST API endpoints (/api/media)
  • MediaService - File upload, download, and media management
  • ReportService - Assessment report generation and PDF creation
  • DigitalOceanProvider - Cloud storage interface (S3-compatible)
  • MediaDBService - Database operations and media tracking

Media Endpoints

MethodEndpointAuthRolesDescription
POST/upload-publicJWTAllUpload files to public bucket
POST/upload-privateJWTAllUpload files to private bucket
GET/download-private/:idJWTAllDownload private media files
GET/download-public/:idJWTAllDownload public media files
POST/clear-unused-mediaJWTADMINClean up unused media files
POST/upload-generate-reportJWTAllGenerate assessment reports
POST/generate-reportJWTAllGenerate unified reports

File Upload (Public)

Upload files to public S3 bucket with database tracking.

// Request: multipart/form-data
{
files: File[] // Array of files to upload
}

// Response
{
statusCode: 200,
message: "Files uploaded successfully",
data: [
{
id: "media-uuid",
path: "https://cdn.comdeall.com/file-uuid.pdf",
type: "pdf",
bucket_type: "public",
file_name: "original-name-uuid.pdf"
}
]
}

Generate Unified Report

Create assessment reports with automatic type detection.

// Request
{
"input": {
"obj": {
"childId": "child-uuid",
"assessmentId": "assessment-uuid"
}
}
}

// Response
{
success: "SUCCESS",
message: "Report generated successfully",
data: {
consolidatedReport: "https://cdn.comdeall.com/consolidated-report.pdf",
individualReport: "https://cdn.comdeall.com/individual-report.pdf"
}
}

Core Features

1. Media Management

  • Multi-bucket Support - Public and private file storage
  • File Type Validation - Automatic type detection and validation
  • UUID-based Naming - Secure file naming with collision prevention
  • Usage Tracking - Database tracking of media usage and references
  • Bulk Operations - Multiple file upload and deletion support

2. Report Generation

  • Assessment Reports - Automated PDF generation for completed assessments
  • Multiple Formats - ORO, PLS, CDDC, and Generic assessment reports
  • Cover Pages - Professional report covers with child and provider details
  • Template System - Modular report templates for different assessment types
  • Age-based Logic - Dynamic content based on child's age and development

3. Storage Integration

  • DigitalOcean Spaces - S3-compatible cloud storage
  • Presigned URLs - Secure access to private files
  • CDN Distribution - Fast global content delivery
  • Automatic Cleanup - Scheduled removal of unused media files

4. Advanced Features

  • PDF Merging - Combine cover pages with report content
  • Responsive Templates - Mobile and print-optimized layouts
  • Error Recovery - Comprehensive error handling and logging
  • Performance Optimization - Async processing and batching

Media Management

Upload Workflow

File Selection → Validation → S3 Upload → Database Record → Usage Tracking → URL Generation

Process Steps:

  1. Client Upload - Files sent via multipart/form-data
  2. Type Detection - Automatic MIME type and extension validation
  3. UUID Generation - Unique filename creation with collision prevention
  4. S3 Storage - Upload to appropriate bucket (public/private)
  5. Database Tracking - Media record creation with metadata
  6. URL Response - CDN URLs returned to client

Download & Access

  • Public Files - Direct CDN access with caching
  • Private Files - Presigned URL generation with expiration
  • Stream Processing - Efficient file streaming for large files
  • Access Control - Role-based download permissions

Media Cleanup System

// Automated cleanup process
{
unusedMedia: {
criteria: "NOT referenced in any active records",
frequency: "Manual trigger by ADMIN",
actions: ["Delete from S3", "Remove database record"]
}
}

Report Generation System

Report Types

Individual Reports

Single assessment reports for specific domains or subjects.

  • ORO Motor Assessment - Oral motor skills evaluation
  • PLS Assessment - Preschool Language Scale evaluation
  • CDDC Domain - Single domain developmental assessment
  • Generic Assessment - Flexible format for other assessment types

Consolidated Reports

Multi-domain summary reports combining multiple assessments.

  • CDDC Consolidated - All 8 CDDC domains combined
  • Progress Reports - Longitudinal assessment comparisons
  • Custom Reports - User-defined report combinations

Report Generation Workflow

Assessment Completion → Data Extraction → Template Selection → PDF Generation → S3 Upload → Database Tracking → URL Response

Detailed Process:

  1. Trigger - Assessment marked as completed
  2. Data Collection - Retrieve assessment responses and child details
  3. Type Detection - Determine appropriate report template
  4. Score Calculation - Apply assessment-specific scoring logic
  5. Template Rendering - Generate HTML from template with data
  6. PDF Creation - Use Puppeteer to convert HTML to PDF
  7. Cover Generation - Create professional cover page
  8. Merge Process - Combine cover and content into final PDF
  9. Storage - Upload to S3 and create database record
  10. Notification - Return download URL to client

Assessment-Specific Logic

ORO Motor Assessment

// Scoring sections: Jaw, Tongue, Lip Movement, Speech
{
sections: ["Jaw Movement", "Tongue Movement", "Lip Movement", "Speech"],
scoring: "Age-based thresholds with adequate/inadequate classification",
ageRanges: ["0-24 months", "25-36 months", "37+ months"]
}

PLS Assessment

// High/Low interest scoring with age-based validation
{
interests: ["High Interest", "Low Interest"],
scoring: "Section-based totals with adequacy thresholds",
validation: "Minimum score requirements per age group"
}

CDDC Assessment

// 8-domain developmental assessment with age-based filtering
{
domains: [
"Gross Motor", "Fine Motor", "Activities of Daily Living",
"Receptive Language", "Expressive Language", "Cognitive Skills",
"Social Skills", "Emotional Skills"
],
scoring: "Baseline and scattered age calculation",
filtering: "Question limit based on child age (1-72 months)"
}

Assessment Report Types

Report Type Classification

enum AssessmentReportType {
INDIVIDUAL = 'INDIVIDUAL', // Single assessment domain
CONSOLIDATED = 'CONSOLIDATED' // Multiple domains combined
}

enum ReportType {
COVER = 'COVER', // Report cover page
REPORT = 'REPORT' // Main report content
}

Template System

  • Cover Templates - Standardized cover pages with branding
  • Content Templates - Assessment-specific layouts and styling
  • Modular Design - Reusable components for different report types
  • Responsive Layout - Print and screen optimized formatting

Age-based Logic Implementation

// CDDC age-based question filtering
const getChildQuestionLimit = (ageInMonths: number): number => {
// Age quadrants: 1-2, 3-4, 5-6, ..., 71-72 months
const quadrants = [
{minimum: 0, maximum: 2}, {minimum: 3, maximum: 4},
// ... continues for all age ranges
];

const matchingQuadrant = quadrants.find(
q => ageInMonths >= q.minimum && ageInMonths <= q.maximum
);

return matchingQuadrant ? quadrants.indexOf(matchingQuadrant) + 1 : quadrants.length;
};

CDDC Scattering Logic

The CDDC (Child Development Diagnostic Checklist) assessment uses sophisticated scattering logic to determine a child's developmental baseline and scattered skills across age ranges. This logic is critical for accurately interpreting developmental progress and identifying areas of strength and concern.

Conceptual Framework

Development Pattern Analysis

CDDC assessments evaluate development across age-based milestones using a scoring system:

  • Score 4 - Skill mastered/consistently demonstrated
  • Score 3 - Emerging skill/inconsistent performance
  • Score 2 - Skill attempted but not mastered
  • Score 1 - Skill not yet emerging
  • Score 0 - No response/skill not observed

Key Terminology

  • Baseline Age - The highest age at which all skills are consistently mastered (score 4)
  • Scattered Skills - Skills that are emerging or partially mastered (scores 1-3) beyond the baseline
  • Age Intervals - 2-month increments (1-2 months, 3-4 months, etc.)

Scattering Algorithm Implementation

Phase 1: Data Preprocessing

// Convert all responses to numeric format
const numericScores = scores.map((score) => {
if (typeof score === 'string') {
if (score.toLowerCase() === 'nr') return 0; // No Response
const parsed = parseInt(score);
return isNaN(parsed) ? 0 : parsed;
}
return typeof score === 'number' ? score : 0;
});

Phase 2: Pattern Detection

// Detect if assessment starts with scattered skills (scores 1-3)
let startsWithScattered = false;
if (numericScores.length > 0 && numericScores[0] >= 1 && numericScores[0] <= 3) {
startsWithScattered = true;
}

// Find where consistent mastery (score 4) breaks
let baselineAge = startAgeMonths;
let foundBreakInFours = false;

if (!startsWithScattered) {
for (let i = 0; i < numericScores.length; i++) {
const currentAge = startAgeMonths + i * intervalMonths;
const score = numericScores[i];

if (score !== 4 && !foundBreakInFours) {
foundBreakInFours = true;
baselineAge = currentAge;
break;
}
}
}

Phase 3: Three Consecutive Zeros Rule

Critical pattern that indicates cessation of developmental skills:

// Identify three consecutive zeros pattern
let hasThreeConsecutiveZeros = false;
let threeZeroStartAge = startAgeMonths;

for (let i = 0; i <= numericScores.length - 3; i++) {
if (numericScores[i] === 0 && numericScores[i + 1] === 0 && numericScores[i + 2] === 0) {
hasThreeConsecutiveZeros = true;
threeZeroStartAge = startAgeMonths + i * intervalMonths;
break;
}
}

Phase 4: Scattered Age Calculation

let scatteredAge = startAgeMonths;

if (hasThreeConsecutiveZeros) {
// Find scattered skills before the three consecutive zeros
for (let i = 0; i < numericScores.length; i++) {
const currentAge = startAgeMonths + i * intervalMonths;
const score = numericScores[i];

if (currentAge >= threeZeroStartAge) break;

if (score >= 1 && score <= 3) {
scatteredAge = threeZeroStartAge; // Scattered till where three zeros start
}
}
} else {
// Find last occurrence of scattered skills (scores 1-3)
let lastScatteredIndex = -1;
for (let i = 0; i < numericScores.length; i++) {
if (numericScores[i] >= 1 && numericScores[i] <= 3) {
lastScatteredIndex = i;
}
}

if (lastScatteredIndex >= 0) {
scatteredAge = startAgeMonths + lastScatteredIndex * intervalMonths;
}
}

Summary Generation Logic

Scenario-based Summary Creation

The algorithm generates different summary formats based on detected patterns:

let summary = '';

// Scenario 1: Assessment starts with scattered skills
if (startsWithScattered) {
if (!hasThreeConsecutiveZeros) {
// Check if scattered skills continue to assessment end
let lastScatteredIndex = -1;
for (let i = 0; i < numericScores.length; i++) {
if (numericScores[i] >= 1 && numericScores[i] <= 3) {
lastScatteredIndex = i;
}
}

if (lastScatteredIndex >= 0 && lastScatteredIndex >= numericScores.length - 3) {
summary = "Scattered till age"; // Skills continue to assessment end
} else if (scatteredAge > startAgeMonths) {
summary = `Scattered till ${scatteredAge} months`;
}
} else {
summary = `Scattered till ${scatteredAge} months`;
}
}

// Scenario 2: Clear baseline with potential scattered skills
else if (baselineAge > startAgeMonths) {
summary = `${baselineAge} months`;

if (hasThreeConsecutiveZeros && scatteredAge > baselineAge) {
summary += ` + Scattered till ${scatteredAge} months`;
} else if (!hasThreeConsecutiveZeros && scatteredAge > baselineAge) {
summary += ` + Scattered till ${scatteredAge} months`;
}
}

// Scenario 3: Only scattered skills present
else if (scatteredAge > startAgeMonths) {
summary = `Scattered till ${scatteredAge} months`;
}

// Scenario 4: No significant development detected
else {
summary = 'No significant development detected';
}

Clinical Interpretation Examples

Example: Typical Development Pattern

Scores: [4, 4, 4, 4, 3, 2, 1, 0, 0, 0] Ages: [1-2, 3-4, 5-6, 7-8, 9-10, 11-12, 13-14, 15-16, 17-18, 19-20] months Analysis: Baseline: 7-8 months (last consistent score of 4) Scattered: 13-14 months (last score of 1-3) Three consecutive zeros: Yes (starts at 15-16 months) Summary: "8 months + Scattered till 14 months"

Media Usage Tracking

  • Reference Counting - Track which records reference each media file
  • Orphan Detection - Identify unused media for cleanup
  • Usage Flags - Boolean tracking for active media
  • Relationship Mapping - Link media to children, assessments, and reports

Performance Indexes

-- Media query optimization
CREATE INDEX idx_media_bucket_type ON media (bucket_type);
CREATE INDEX idx_media_in_use ON media (in_use);
CREATE INDEX idx_media_created_at ON media (created_at);
CREATE INDEX idx_child_media_child_id ON child_media (child_id);
CREATE INDEX idx_assessment_report_child_id ON assessment_report (child_id);

Storage Architecture

DigitalOcean Spaces Integration

// S3-compatible configuration
{
provider: "DigitalOcean Spaces",
compatibility: "AWS S3 API",
regions: ["nyc3", "ams3", "sgp1"],
features: ["CDN", "Object Storage", "Presigned URLs"]
}

Bucket Strategy

  • Public Bucket - Publicly accessible files (reports, images)
  • Private Bucket - Restricted access files (personal documents)
  • CDN Integration - Global content delivery network
  • Access Control - Bucket-level and object-level permissions

File Naming Convention

// UUID-based naming for security and uniqueness
const generateFileName = (originalName: string): string => {
const fileID = uuidv4();
const extension = originalName.substring(originalName.lastIndexOf('.'));
const nameWithoutExt = originalName.substring(0, originalName.lastIndexOf('.'));
return `${nameWithoutExt}-${fileID}${extension}`;
};

Upload Process

  1. Validation - File type, size, and format verification
  2. UUID Generation - Unique identifier for collision prevention
  3. S3 Upload - Direct upload to appropriate bucket
  4. Database Record - Media metadata storage
  5. URL Generation - CDN URL creation and response

Security & Performance

Access Control

  • JWT Authentication - Required for all media operations
  • Role-based Permissions - Different access levels per user type
  • Presigned URLs - Time-limited access to private files
  • Bucket Isolation - Separate public/private storage areas

File Security

  • UUID Naming - Prevents file enumeration attacks
  • Type Validation - MIME type and extension verification
  • Size Limits - Configurable upload size restrictions
  • Virus Scanning - Integration-ready malware detection

Performance Optimizations

  • CDN Delivery - Global content distribution
  • Async Processing - Non-blocking report generation
  • Batch Operations - Multiple file upload/delete support
  • Connection Pooling - Efficient S3 connection management
  • Caching Strategy - Browser and CDN caching headers

Error Handling

  • Graceful Degradation - Fallback mechanisms for failures
  • Retry Logic - Automatic retry for transient failures
  • Comprehensive Logging - Detailed error tracking and debugging
  • User Feedback - Clear error messages for client applications

Technical Implementation

Media Service Architecture

@Injectable()
export class MediaService {
async uploadMultipleFiles(files: Express.Multer.File[], userId: string, isPrivate: boolean) {
// Upload files to S3 with UUID naming
const uploadPromises = files.map(file =>
this.storageProvider.upload(file, userId, isPrivate)
);
return await Promise.all(uploadPromises);
}

async downloadMediaById(id: string, bucketType: BucketType, res: Response) {
// Stream file from S3 to client
const media = await this.mediaDBService.getMedia(id);
if (bucketType === BucketType.PRIVATE) {
await this.storageProvider.singleDownloadFromPrivateS3(media.file_name, res);
} else {
await this.storageProvider.singleDownloadFromPublicS3(media.file_name, res);
}
}
}

Report Generation Pipeline

@Injectable()
export class ReportService {
async handleUnifiedReport(body: ReportDataDto, user: UserInfo) {
// 1. Data collection and validation
const [childDetails, assessmentType] = await Promise.all([
this.getChildDetails(body.childId),
this.getAssessmentType(body.assessmentId)
]);

// 2. Assessment-specific processing
switch (assessmentType.type) {
case 'ORO':
return await this.generateOROReport(assessmentData);
case 'PLS':
return await this.generatePLSReport(assessmentData);
case 'CDDC':
return await this.generateCDDCReport(assessmentData);
default:
return await this.generateGenericReport(assessmentData);
}
}

private async generatePDF(htmlContent: string): Promise<Buffer> {
// Puppeteer PDF generation with optimization
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle2' });

const pdf = await page.pdf({
format: 'a4',
printBackground: true,
preferCSSPageSize: true
});

await browser.close();
return pdf;
}
}

Storage Provider Interface

interface StorageProvider {
upload(file: Express.Multer.File, userId: string, isPrivate: boolean): Promise<UploadResult>;
singleDownloadFromPrivateS3(fileName: string, res: Response): Promise<void>;
singleDownloadFromPublicS3(fileName: string, res: Response): Promise<void>;
multipleDeleteFromPrivateS3(fileNames: string[]): Promise<void>;
multipleDeleteFromPublicS3(fileNames: string[]): Promise<void>;
createPresignedUrl(fileName: string): Promise<string>;
}

Error Recovery Mechanisms

  • Upload Failures - Automatic retry with exponential backoff
  • PDF Generation Errors - Fallback templates and error reporting
  • S3 Connectivity Issues - Connection pooling and timeout handling
  • Database Consistency - Transaction rollback on failures

This Media & Reports module provides a robust, scalable solution for file management and automated report generation, integrating seamlessly with Comdeall's assessment system to deliver professional-quality documentation and media services.