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.