error("Weather data directory not found: {$basePath}"); return self::FAILURE; } $dateFilter = $this->option('date'); $typeFilter = $this->option('type'); $force = $this->option('force'); $dateFolders = File::directories($basePath); if ($dateFilter) { $dateFolders = array_filter($dateFolders, fn ($dir) => basename($dir) === $dateFilter); } if (empty($dateFolders)) { $this->warn('No date folders found to process.'); return self::SUCCESS; } $this->info('Starting weather data ingestion...'); $progressBar = $this->output->createProgressBar(count($dateFolders)); $totalImported = 0; $totalSkipped = 0; foreach ($dateFolders as $dateFolder) { $date = basename($dateFolder); $files = File::files($dateFolder); foreach ($files as $file) { $filename = $file->getFilename(); if (! str_ends_with($filename, '.json')) { continue; } $type = $this->getReportType($filename); if ($type === null || ($typeFilter && $type !== $typeFilter)) { continue; } $reportedAt = $this->parseReportedAt($date, $filename); $existingReport = WeatherReport::query() ->where('type', $type) ->where('reported_at', $reportedAt) ->first(); if ($existingReport && ! $force) { $totalSkipped++; continue; } $jsonContent = File::get($file->getPathname()); $data = json_decode($jsonContent, true); if (! $data) { $this->warn("Failed to parse JSON: {$file->getPathname()}"); continue; } if ($existingReport && $force) { $existingReport->delete(); } $this->importReport($type, $reportedAt, $data); $totalImported++; } $progressBar->advance(); } $progressBar->finish(); $this->newLine(2); $this->info("Imported: {$totalImported} reports, Skipped: {$totalSkipped} existing"); return self::SUCCESS; } private function getReportType(string $filename): ?string { if (str_starts_with($filename, 'hourly_')) { return 'hourly'; } if (str_starts_with($filename, 'weekly_')) { return 'weekly'; } return null; } private function parseReportedAt(string $date, string $filename): Carbon { preg_match('/(\d{2})-(\d{2})\.json$/', $filename, $matches); $hour = $matches[1] ?? '00'; $minute = $matches[2] ?? '00'; return Carbon::parse("{$date} {$hour}:{$minute}:00"); } /** * @param array $data */ private function importReport(string $type, Carbon $reportedAt, array $data): void { $properties = $data['properties'] ?? []; $geometry = $data['geometry'] ?? []; $coordinates = $geometry['coordinates'][0] ?? []; $centerLat = 0; $centerLng = 0; if (! empty($coordinates)) { $lats = array_column($coordinates, 1); $lngs = array_column($coordinates, 0); $centerLat = array_sum($lats) / count($lats); $centerLng = array_sum($lngs) / count($lngs); } $report = WeatherReport::create([ 'type' => $type, 'reported_at' => $reportedAt, 'generated_at' => Carbon::parse($properties['generatedAt'] ?? $properties['updateTime'] ?? $reportedAt), 'latitude' => $centerLat, 'longitude' => $centerLng, 'elevation_meters' => $properties['elevation']['value'] ?? null, ]); $periods = $properties['periods'] ?? []; foreach ($periods as $period) { WeatherPeriod::create([ 'weather_report_id' => $report->id, 'period_number' => $period['number'], 'name' => $period['name'] ?: null, 'start_time' => Carbon::parse($period['startTime']), 'end_time' => Carbon::parse($period['endTime']), 'is_daytime' => $period['isDaytime'], 'temperature' => $period['temperature'], 'temperature_unit' => $period['temperatureUnit'], 'precipitation_probability' => $period['probabilityOfPrecipitation']['value'] ?? null, 'dewpoint_celsius' => isset($period['dewpoint']['value']) ? round($period['dewpoint']['value'], 2) : null, 'relative_humidity' => $period['relativeHumidity']['value'] ?? null, 'wind_speed' => $period['windSpeed'] ?? null, 'wind_direction' => $period['windDirection'] ?? null, 'icon_url' => $period['icon'] ?? null, 'short_forecast' => $period['shortForecast'] ?? null, 'detailed_forecast' => $period['detailedForecast'] ?: null, ]); } } }