sanitizeDescription($data['contents'] ?? ''), location: $this->parseLocation($data), salaryMin: null, // The Muse doesn't provide salary info salaryMax: null, salaryCurrency: 'USD', jobType: $this->mapJobType($data['type'] ?? null), sourceUrl: $data['refs']['landing_page'] ?? null, applyUrl: $data['refs']['landing_page'] ?? null, postedAt: $this->parseDate($data['publication_date'] ?? null), expiresAt: null, remoteAllowed: $this->isRemote($data), experienceLevel: $this->mapExperienceLevel($data['levels'] ?? []), category: $this->parseCategory($data['categories'] ?? []), rawData: $data, ); } catch (\Throwable $e) { throw new TransformationException( 'The Muse', 'Failed to transform The Muse job data: ' . $e->getMessage(), ['data' => $data, 'error' => $e->getMessage()] ); } } public function transformMany(array $jobs): array { return array_map(fn($job) => $this->transform($job), $jobs); } private function sanitizeDescription(string $description): string { // $clean = strip_tags($description); // $clean = html_entity_decode($clean, ENT_QUOTES | ENT_HTML5, 'UTF-8'); // $clean = preg_replace('/\s+/', ' ', $clean); return trim($description); } private function parseLocation(array $data): ?string { if (empty($data['locations'])) { return null; } $locations = array_map(fn($loc) => $loc['name'] ?? '', $data['locations']); return implode(', ', array_filter($locations)); } private function isRemote(array $data): bool { if (empty($data['locations'])) { return false; } foreach ($data['locations'] as $location) { $name = strtolower($location['name'] ?? ''); if (str_contains($name, 'remote') || str_contains($name, 'flexible')) { return true; } } return false; } private function parseDate(?string $date): ?DateTime { if (empty($date)) { return null; } try { return new DateTime($date); } catch (\Exception $e) { return null; } } private function mapJobType(?string $type): ?string { if (empty($type)) { return 'full-time'; } return match (strtolower($type)) { 'full-time', 'fulltime' => 'full-time', 'part-time', 'parttime' => 'part-time', 'contract' => 'contract', 'temporary', 'temp' => 'temporary', 'internship', 'intern' => 'internship', 'freelance' => 'freelance', default => 'full-time', }; } private function mapExperienceLevel(array $levels): ?string { if (empty($levels)) { return null; } // The Muse uses: Entry Level, Mid Level, Senior Level $firstLevel = strtolower($levels[0]['name'] ?? ''); return match (true) { str_contains($firstLevel, 'entry') => 'entry', str_contains($firstLevel, 'mid') => 'mid', str_contains($firstLevel, 'senior') => 'senior', str_contains($firstLevel, 'executive') => 'executive', default => null, }; } private function parseCategory(array $categories): ?string { if (empty($categories)) { return null; } // Return the first category name return $categories[0]['name'] ?? null; } }