$collections */ public function __construct( public int $id, public string $title, public string $briefing, public string $model, public array $collections, public int $contextLimit, public GenerationStatus $generationStatus, public CritiqueStatus $critiqueStatus, public ?int $authorProfileId, public ?int $contractId, public ?int $structureId, public \DateTimeImmutable $createdAt, public ?\DateTimeImmutable $updatedAt, public ?int $latestVersionId = null, public ?string $authorProfileName = null, public ?string $contractName = null, public ?string $structureName = null, ) {} /** * Create from database row. * * @param array $row */ public static function fromDatabaseRow(array $row): self { $id = isset($row['id']) ? (int) $row['id'] : 0; $title = isset($row['title']) ? (string) $row['title'] : ''; $briefing = isset($row['briefing']) ? (string) $row['briefing'] : ''; $model = isset($row['model']) ? (string) $row['model'] : ''; $collections = self::parseCollections($row['collections'] ?? ''); $contextLimit = isset($row['context_limit']) ? (int) $row['context_limit'] : 3; $generationStatusStr = isset($row['generation_status']) ? (string) $row['generation_status'] : 'idle'; $critiqueStatusStr = isset($row['critique_status']) ? (string) $row['critique_status'] : 'idle'; $createdAtStr = isset($row['created_at']) ? (string) $row['created_at'] : 'now'; $updatedAtStr = isset($row['updated_at']) ? (string) $row['updated_at'] : null; return new self( id: $id, title: $title, briefing: $briefing, model: $model, collections: $collections, contextLimit: $contextLimit, generationStatus: GenerationStatus::from($generationStatusStr), critiqueStatus: CritiqueStatus::from($critiqueStatusStr), authorProfileId: isset($row['author_profile_id']) ? (int) $row['author_profile_id'] : null, contractId: isset($row['contract_id']) ? (int) $row['contract_id'] : null, structureId: isset($row['structure_id']) ? (int) $row['structure_id'] : null, createdAt: new \DateTimeImmutable($createdAtStr), updatedAt: $updatedAtStr !== null ? new \DateTimeImmutable($updatedAtStr) : null, latestVersionId: isset($row['latest_version_id']) ? (int) $row['latest_version_id'] : null, authorProfileName: isset($row['author_profile_name']) ? (string) $row['author_profile_name'] : null, contractName: isset($row['contract_name']) ? (string) $row['contract_name'] : null, structureName: isset($row['structure_name']) ? (string) $row['structure_name'] : null, ); } /** * Parse comma-separated collections or JSON array. * * @return array */ private static function parseCollections(mixed $collections): array { if (is_array($collections)) { /** @var array $collections */ return array_values(array_map(fn(mixed $v): string => (string) $v, $collections)); } if (!is_string($collections) || $collections === '') { return []; } // Try JSON first $decoded = json_decode($collections, true); if (is_array($decoded)) { return array_values(array_map(fn(mixed $v): string => (string) $v, $decoded)); } // Fall back to comma-separated return array_values(array_filter(array_map('trim', explode(',', $collections)))) } /** * Check if generation can be started. */ public function canStartGeneration(): bool { return $this->generationStatus->canStart(); } /** * Check if critique can be started. */ public function canStartCritique(): bool { return $this->critiqueStatus->canStart() && $this->latestVersionId !== null; } }