$row */ public static function fromDatabaseRow(array $row): self { return new self( id: (int) $row['id'], pipelineId: (int) $row['pipeline_id'], filename: (string) $row['filename'], filepath: (string) $row['filepath'], mimeType: (string) ($row['mime_type'] ?? 'application/octet-stream'), fileSize: (int) ($row['file_size'] ?? 0), status: DocumentStatus::from((string) ($row['status'] ?? 'pending')), errorMessage: $row['error_message'] ?? null, chunkCount: (int) ($row['chunk_count'] ?? 0), createdAt: new \DateTimeImmutable($row['created_at'] ?? 'now'), processedAt: isset($row['processed_at']) ? new \DateTimeImmutable($row['processed_at']) : null, ); } /** * Check if document can be reprocessed. */ public function canReprocess(): bool { return $this->status->canReprocess(); } /** * Check if document is currently processing. */ public function isProcessing(): bool { return $this->status->isActive(); } /** * Get file size in human-readable format. */ public function fileSizeFormatted(): string { $units = ['B', 'KB', 'MB', 'GB']; $size = $this->fileSize; $unitIndex = 0; while ($size >= 1024 && $unitIndex < count($units) - 1) { $size /= 1024; $unitIndex++; } return round($size, 2) . ' ' . $units[$unitIndex]; } }