From 332a96601be77cffa5245ea736d736bd90fd1945 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sat, 28 Mar 2026 15:37:40 +0900 Subject: [PATCH 1/3] chore: remove redundant files --- .../Console/Commands/BackfillStateParties.php | 76 --- .../Commands/BackfillThumbnailImageId.php | 43 -- .../Commands/BuildWorldHeritageLocalDb.php | 105 ---- .../Commands/ImportWorldHeritageFromJson.php | 290 --------- src/app/Console/Commands/SplitCountryJson.php | 582 ------------------ .../Commands/SplitWorldHeritageImageJson.php | 226 ------- 6 files changed, 1322 deletions(-) delete mode 100644 src/app/Console/Commands/BackfillStateParties.php delete mode 100644 src/app/Console/Commands/BackfillThumbnailImageId.php delete mode 100644 src/app/Console/Commands/BuildWorldHeritageLocalDb.php delete mode 100644 src/app/Console/Commands/ImportWorldHeritageFromJson.php delete mode 100644 src/app/Console/Commands/SplitCountryJson.php delete mode 100644 src/app/Console/Commands/SplitWorldHeritageImageJson.php diff --git a/src/app/Console/Commands/BackfillStateParties.php b/src/app/Console/Commands/BackfillStateParties.php deleted file mode 100644 index 1f15bcf..0000000 --- a/src/app/Console/Commands/BackfillStateParties.php +++ /dev/null @@ -1,76 +0,0 @@ -whereNotNull('state_party'); - - if ($ids = $this->option('site-id')) { - $query->whereIn('id', $ids); - } - - $processed = 0; - - $query->orderBy('id')->chunkById(500, function ($sites) use (&$processed) { - foreach ($sites as $site) { - $codes = collect(preg_split('/[;,\s]+/', strtoupper((string) $site->state_party))) - ->filter(fn($c) => strlen($c) === 2) - ->values(); - - if ($codes->isEmpty()) { - continue; - } - - $valid = Country::whereIn('state_party_code', $codes)->pluck('state_party_code')->all(); - $missing = array_diff($codes->all(), $valid); - if ($missing !== []) { - Log::warning("Unknown codes for site {$site->id}: ".implode(',', $missing)); - } - - $payload = []; - foreach ($valid as $i => $code) { - $payload[$code] = [ - 'is_primary' => $i === 0, - 'inscription_year' => $site->year_inscribed, - ]; - } - - if ($this->option('dry-run')) { - $this->line("[dry-run] site {$site->id} -> ".json_encode($payload)); - } else { - $site->countries()->syncWithoutDetaching($payload); - } - - $processed++; - } - }); - - $this->info("Processed {$processed} site(s)."); - return self::SUCCESS; - } -} diff --git a/src/app/Console/Commands/BackfillThumbnailImageId.php b/src/app/Console/Commands/BackfillThumbnailImageId.php deleted file mode 100644 index b8f937e..0000000 --- a/src/app/Console/Commands/BackfillThumbnailImageId.php +++ /dev/null @@ -1,43 +0,0 @@ -option('dry-run'); - - WorldHeritage::chunk(100, function ($sites) use ($dryRun) { - foreach ($sites as $site) { - $image = $site->images()->orderBy('sort_order')->first(); - - if (! $image) { - Log::warning('WorldHeritage has no images', ['id' => $site->id]); - $this->warn("Site {$site->id} has no images"); - continue; - } - - if ($site->thumbnail_image_id === $image->id) { - continue; - } - - $this->info("Site {$site->id}: thumbnail_image_id -> {$image->id}"); - - if (! $dryRun) { - $site->thumbnail_image_id = $image->id; - $site->save(); - } - } - }); - - return Command::SUCCESS; - } -} diff --git a/src/app/Console/Commands/BuildWorldHeritageLocalDb.php b/src/app/Console/Commands/BuildWorldHeritageLocalDb.php deleted file mode 100644 index a07e143..0000000 --- a/src/app/Console/Commands/BuildWorldHeritageLocalDb.php +++ /dev/null @@ -1,105 +0,0 @@ - import)'; - - /** - * Execute the console command. - */ - public function handle(): int - { - $in = (string) $this->option('in'); - $out = (string) $this->option('out'); - $pretty = (bool) $this->option('pretty'); - $clean = (bool) $this->option('clean'); - - $siteJudgementsOut = rtrim($out, '/') . '/site-country-judgements.json'; - $exceptionsOut = rtrim($out, '/') . '/exceptions-missing-iso-codes.json'; - - $this->info('Running: optimize:clear'); - $this->mustRun('optimize:clear'); - - if (!(bool) $this->option('skip-migrate')) { - $this->info('Running: migrate:fresh'); - $this->mustRun('migrate:fresh'); - } - - $this->info('Running: world-heritage:split-json'); - $splitArgs = [ - '--in' => $in, - '--out' => $out, - '--site-judgements-out' => $siteJudgementsOut, - '--exceptions-out' => $exceptionsOut, - ]; - if ($pretty) { - $splitArgs['--pretty'] = true; - } - if ($clean) { - $splitArgs['--clean'] = true; - } - $this->mustRun('world-heritage:split-json', $splitArgs); - - $this->info('Running: import-countries-split'); - $this->mustRun('world-heritage:import-countries-split', [ - '--in' => rtrim($out, '/') . '/countries.json', - ]); - - $this->info('Running: import-sites-split'); - $this->mustRun('world-heritage:import-sites-split', [ - '--in' => rtrim($out, '/') . '/world_heritage_sites.json', - ]); - - $this->info('Running: import-site-state-parties-split'); - $this->mustRun('world-heritage:import-site-state-parties-split', [ - '--in' => rtrim($out, '/') . '/site_state_parties.json', - ]); - - $this->info('Running: import-images-json'); - $this->mustRun('world-heritage:import-images-json', [ - '--path' => rtrim($out, '/') . '/world_heritage_site_images.json', - ]); - - $this->info('Running: import-site-country-exceptions'); - $this->mustRun('world-heritage:import-site-country-exceptions', [ - '--in' => rtrim($out, '/') . '/exceptions-missing-iso-codes.json', - ]); - - $this->info('Done'); - return self::SUCCESS; - } - - private function mustRun(string $command, array $args = []): void - { - $code = Artisan::call($command, $args); - $this->output->write(Artisan::output()); - - if ($code !== 0) { - $this->error("Command failed: {$command}"); - exit($code); - } - } -} diff --git a/src/app/Console/Commands/ImportWorldHeritageFromJson.php b/src/app/Console/Commands/ImportWorldHeritageFromJson.php deleted file mode 100644 index fdf534e..0000000 --- a/src/app/Console/Commands/ImportWorldHeritageFromJson.php +++ /dev/null @@ -1,290 +0,0 @@ -option('path'); - $max = (int) $this->option('max'); - $batchSize = max(1, (int) $this->option('batch')); - - $fullPath = $this->resolvePath($path); - - if (!file_exists($fullPath)) { - $this->error("Path not found: {$fullPath}"); - return self::FAILURE; - } - - $files = $this->collectJsonFiles($fullPath); - if ($files === []) { - $this->error("No JSON files found: {$fullPath}"); - return self::FAILURE; - } - - $imported = 0; - $skipped = 0; - $batch = []; - $now = Carbon::now(); - - foreach ($files as $filePath) { - if ($max > 0 && $imported >= $max) { - break; - } - - $results = $this->loadResultsFromJsonFile($filePath); - if ($results === null) { - $this->warn("Skipped invalid JSON: {$filePath}"); - continue; - } - - foreach ($results as $row) { - if ($max > 0 && $imported >= $max) { - break; - } - if (!is_array($row)) { $skipped++; continue; } - - $mapped = $this->mapFromUnescoApiRow($row); - - if (empty($mapped['id'])) { $skipped++; continue; } - - $mapped['updated_at'] = $now; - $mapped['created_at'] ??= $now; - - $batch[] = $mapped; - - if (count($batch) >= $batchSize) { - $imported += $this->flushBatch($batch); - $batch = []; - } - } - } - - if ($batch !== []) { - $imported += $this->flushBatch($batch); - } - - $this->info("Imported/updated {$imported} records. Skipped {$skipped} items."); - return self::SUCCESS; - } - - private function resolvePath(string $path): string - { - if ($path !== '' && ($path[0] === '/' || preg_match('/^[A-Za-z]:\\\\/', $path) === 1)) { - return $path; - } - return base_path($path); - } - - private function collectJsonFiles(string $fullPath): array - { - if (is_file($fullPath)) { - return str_ends_with($fullPath, '.json') ? [$fullPath] : []; - } - - $files = []; - $rii = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($fullPath, \FilesystemIterator::SKIP_DOTS) - ); - - foreach ($rii as $file) { - if ($file->isFile() && str_ends_with($file->getFilename(), '.json')) { - $files[] = $file->getPathname(); - } - } - - sort($files); - return $files; - } - - private function loadResultsFromJsonFile(string $filePath): ?array - { - $raw = @file_get_contents($filePath); - if ($raw === false) { - return null; - } - - $json = json_decode($raw, true); - if (!is_array($json)) { - return null; - } - - if (array_key_exists('results', $json)) { - return is_array($json['results']) ? $json['results'] : null; - } - - return $json; - } - - private function mapFromUnescoApiRow(array $row): array - { - $id = $row['id_no'] ?? null; - $lat = $row['coordinates']['lat'] ?? null; - $lon = $row['coordinates']['lon'] ?? null; - - $countryName = $this->extractCountryName($row); - $statePartyIso3 = $this->extractIso3StateParty($row); - - return [ - 'id' => $this->toNullableInt($id), - 'official_name' => $row['official_name'] ?? null, - 'name' => $row['name_en'] ?? $row['name'] ?? null, - 'region' => $row['region_en'] ?? $row['region'] ?? null, - 'state_party' => $statePartyIso3, - 'study_region' => StudyRegionResolver::resolveFromCountry($countryName)->value, - 'category' => $row['category'] ?? $row['type'] ?? null, - 'criteria' => $row['criteria'] ?? null, - 'year_inscribed' => $this->toNullableInt($row['date_inscribed'] ?? $row['year_inscribed'] ?? null), - 'area_hectares' => $this->toNullableFloat($row['area_hectares'] ?? null), - 'buffer_zone_hectares' => $this->toNullableFloat($row['buffer_zone_hectares'] ?? null), - 'is_endangered' => $this->toNullableBool($row['danger'] ?? $row['is_endangered'] ?? null), - 'latitude' => $this->toNullableFloat($lat), - 'longitude' => $this->toNullableFloat($lon), - 'short_description' => $row['short_description'] ?? $row['description'] ?? null, - 'image_url' => $row['image_url'] ?? null, - 'thumbnail_image_id' => null, - 'unesco_site_url' => $row['url'] ?? null, - ]; - } - - private function flushBatch(array $batch): int - { - $updateColumns = array_values(array_diff(array_keys($batch[0]), ['id'])); - - WorldHeritage::query()->upsert( - $batch, - ['id'], - $updateColumns - ); - - return count($batch); - } - - private function extractIso3StateParty(array $row): ?string - { - $candidates = []; - - foreach (['primary_state_party_code', 'state_party_code', 'iso3', 'iso_code'] as $k) { - if (!empty($row[$k]) && is_string($row[$k])) { - $candidates[] = $row[$k]; - } - } - - foreach (['state_party_codes', 'states_codes'] as $k) { - if (!empty($row[$k]) && is_array($row[$k])) { - $candidates[] = $row[$k][0] ?? null; - } - } - - $states = $row['states'] ?? $row['state_party'] ?? null; - if (is_string($states)) { - $candidates[] = $states; - } - if (is_array($states)) { - $candidates[] = $states[0] ?? null; - } - - foreach ($candidates as $c) { - if (!is_string($c)) { - continue; - } - $c = strtoupper(trim($c)); - if ($c !== '' && preg_match('/^[A-Z]{3}$/', $c)) { - return $c; - } - } - - return null; - } - - private function extractCountryName(array $row): ?string - { - $states = $row['states'] ?? $row['state_party'] ?? null; - - if (is_string($states)) { - $normalized = trim($states); - return $normalized !== '' ? $normalized : null; - } - - if (is_array($states)) { - $first = $states[0] ?? null; - if (is_string($first)) { - $normalized = trim($first); - return $normalized !== '' ? $normalized : null; - } - } - - return null; - } - - private function toNullableInt(mixed $v): ?int - { - if ($v === null || $v === '') { - return null; - } - return is_numeric($v) ? (int) $v : null; - } - - private function toNullableFloat(mixed $value): ?float - { - if ($value === null || $value === '') { - return null; - } - - if (is_string($value)) { - $value = str_replace(',', '', trim($value)); - if ($value === '') { - return null; - } - } - - return is_numeric($value) ? (float) $value : null; - } - - private function toNullableBool(mixed $value): ?bool - { - if ($value === null || $value === '') { - return null; - } - - if (is_bool($value)) { - return $value; - } - - if (is_int($value) || is_float($value)) { - return ((int) $value) === 1; - } - - if (is_string($value)) { - $v = strtolower(trim($value)); - if ($v === '') { - return null; - } - - $true = ['1', 'true', 't', 'yes', 'y', 'on']; - $false = ['0', 'false', 'f', 'no', 'n', 'off']; - - if (in_array($v, $true, true)) { - return true; - } - if (in_array($v, $false, true)) { - return false; - } - } - - return null; - } -} diff --git a/src/app/Console/Commands/SplitCountryJson.php b/src/app/Console/Commands/SplitCountryJson.php deleted file mode 100644 index eccfb5b..0000000 --- a/src/app/Console/Commands/SplitCountryJson.php +++ /dev/null @@ -1,582 +0,0 @@ -option('in')); - $out = trim((string) $this->option('out')); - $sitesOut = trim((string) $this->option('sites-out')); - $exceptionsOut = trim((string) $this->option('exceptions-out')); - - $pretty = (bool) $this->option('pretty'); - $dryRun = (bool) $this->option('dry-run'); - $strict = (bool) $this->option('strict'); - $mergeExisting = (bool) $this->option('merge-existing'); - $clean = (bool) $this->option('clean'); - $exceptionsLimit = max(0, (int)$this->option('exceptions-limit')); - - if ($in === '') { - $this->error('Missing required option: --in'); - return self::FAILURE; - } - - $inPath = $this->resolvePath($in); - if (!file_exists($inPath)) { - $this->error("Input not found: {$inPath}"); - return self::FAILURE; - } - - $files = $this->collectJsonFiles($inPath); - if ($files === []) { - $this->error("No JSON files found in: {$inPath}"); - return self::FAILURE; - } - - $outStoragePath = ltrim($out, '/'); - $sitesOutStoragePath = ltrim($sitesOut, '/'); - $exceptionsOutStoragePath = ltrim($exceptionsOut, '/'); - - if ($clean && !$dryRun) { - foreach ([$outStoragePath, $sitesOutStoragePath, $exceptionsOutStoragePath] as $p) { - if (Storage::disk('local')->exists($p)) { - Storage::disk('local')->delete($p); - $this->warn("Deleted existing output: storage/app/{$p}"); - } - } - } - - $existingJp = []; - if ($mergeExisting && !$clean) { - $existingJp = $this->readExistingCountriesJpMap($outStoragePath); - if ($existingJp !== []) { - $this->info('Loaded existing name_jp entries: ' . count($existingJp)); - } - } - - $normalizer = app(CountryCodeNormalizer::class); - - $countryMap = []; - $siteJudgements = []; - $exceptions = []; - $exceptionsCount = 0; - $inputRows = 0; - $invalidJsonFiles = 0; - $rowsNotObject = 0; - $rowsMissingCodes = 0; - $rowsUnknownCodes = 0; - $unknownSamples = []; - - foreach ($files as $file) { - $raw = @file_get_contents($file); - if ($raw === false) { - $this->warn("Skipped unreadable file: {$file}"); - $invalidJsonFiles++; - continue; - } - - $json = json_decode($raw, true); - if (!is_array($json)) { - $this->warn("Skipped invalid JSON: {$file}"); - $invalidJsonFiles++; - continue; - } - - $rows = $this->extractRows($json); - if ($rows === null) { - $this->warn("Skipped unknown JSON shape (expected {results:[...]} or [...]): {$file}"); - $invalidJsonFiles++; - continue; - } - - foreach ($rows as $row) { - $inputRows++; - - if (!is_array($row)) { - $rowsNotObject++; - continue; - } - - $idNo = $row['id_no'] ?? null; - $nameEn = $row['name_en'] ?? null; - $statesNames = $row['states_names'] ?? null; - $regionCode = $row['region_code'] ?? null; - $codesRaw = $this->normalizeCodeList($row['states'] ?? $row['iso_codes'] ?? null); - $judgement = [ - 'id_no' => is_scalar($idNo) ? (string)$idNo : null, - 'name_en' => is_scalar($nameEn) ? (string)$nameEn : null, - 'region_code' => is_scalar($regionCode) ? (string)$regionCode : null, - 'states_names' => is_array($statesNames) ? $statesNames : null, - 'raw_codes' => $codesRaw !== [] ? $codesRaw : null, - 'iso3_codes' => null, - 'status' => null, - 'message' => null, - ]; - - if ($codesRaw === []) { - $rowsMissingCodes++; - - $judgement['status'] = 'missing'; - $judgement['message'] = 'iso_codes/states missing or empty'; - $siteJudgements[] = $judgement; - - if ($exceptionsLimit > 0 && $exceptionsCount < $exceptionsLimit) { - $exceptions[] = [ - 'file' => $file, - 'exception_type' => 'missing_country_code', - 'id_no' => $judgement['id_no'], - 'name_en' => $judgement['name_en'], - 'region_code' => $judgement['region_code'], - 'states_names' => $judgement['states_names'], - 'iso_codes' => $row['iso_codes'] ?? null, - 'states' => $row['states'] ?? null, - ]; - $exceptionsCount++; - } - - if ($strict) { - $this->error('Strict mode: missing country code row detected.'); - $this->line(json_encode($judgement, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)); - return self::FAILURE; - } - - continue; - } - - try { - $codes3 = $normalizer->toIso3List($codesRaw); - } catch (InvalidArgumentException $e) { - $rowsUnknownCodes++; - - $judgement['status'] = 'unknown'; - $judgement['message'] = $e->getMessage(); - $siteJudgements[] = $judgement; - - if (count($unknownSamples) < 10) { - $unknownSamples[] = [ - 'file' => $file, - 'input_codes' => $codesRaw, - 'message' => $e->getMessage(), - ]; - } - - if ($exceptionsLimit > 0 && $exceptionsCount < $exceptionsLimit) { - $exceptions[] = [ - 'file' => $file, - 'exception_type' => 'unknown_country_code', - 'id_no' => $judgement['id_no'], - 'name_en' => $judgement['name_en'], - 'region_code' => $judgement['region_code'], - 'states_names' => $judgement['states_names'], - 'raw_codes' => $codesRaw, - 'message' => $e->getMessage(), - ]; - $exceptionsCount++; - } - - if ($strict) { - $this->error('Strict mode: unknown country code detected.'); - $this->line($e->getMessage()); - return self::FAILURE; - } - - continue; - } - - if ($codes3 === []) { - $rowsMissingCodes++; - - $judgement['status'] = 'missing'; - $judgement['message'] = 'empty after normalize'; - $siteJudgements[] = $judgement; - - if ($strict) { - $this->error('Strict mode: empty iso3 after normalize.'); - $this->line(json_encode($judgement, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)); - return self::FAILURE; - } - - continue; - } - - $judgement['status'] = 'ok'; - $judgement['iso3_codes'] = $codes3; - $siteJudgements[] = $judgement; - $names = $this->normalizeStringList($row['states_names'] ?? null); - $region = $this->normalizeRegionCode($row['region_code'] ?? null); - - if ($names !== [] && count($names) === count($codes3)) { - foreach ($codes3 as $idx => $code) { - $en = trim((string)($names[$idx] ?? '')); - if ($en === '') { - $en = $code; - } - - $this->upsertCountryRow( - countryMap: $countryMap, - code: $code, - nameEn: $en, - existingJp: $existingJp, - region: $region - ); - } - continue; - } - - foreach ($codes3 as $code) { - $this->upsertCountryRow( - countryMap: $countryMap, - code: $code, - nameEn: null, - existingJp: $existingJp, - region: $region - ); - } - } - } - - ksort($countryMap, SORT_STRING); - $countries = array_values($countryMap); - - $this->line('----'); - $this->info('Input files: ' . count($files)); - $this->info("Input rows scanned: {$inputRows}"); - $this->info('Countries extracted (unique state_party_code): ' . count($countries)); - $this->info("Site judgements (rows): " . count($siteJudgements)); - $this->info("Invalid JSON files: {$invalidJsonFiles}"); - $this->info("Rows not object: {$rowsNotObject}"); - $this->info("Rows missing country codes: {$rowsMissingCodes}"); - $this->info("Rows unknown country codes: {$rowsUnknownCodes}"); - $this->info("Exceptions collected: " . count($exceptions)); - - if ($unknownSamples !== []) { - $this->warn('Unknown samples (up to 10):'); - foreach ($unknownSamples as $s) { - $this->line('- ' . json_encode($s, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); - } - } - - $countriesPayload = [ - 'meta' => [ - 'schema' => 'countries.v1', - 'source' => 'unesco whc001', - 'country_code_standard' => 'alpha-3', - 'generated_at' => now()->toIso8601String(), - 'input' => $in, - 'input_files' => count($files), - 'input_rows_scanned' => $inputRows, - 'countries' => count($countries), - 'merge_existing_name_jp' => $mergeExisting && !$clean, - 'region_standard' => 'region_code(EUR/AFR/APA/ARB/LAC)', - ], - 'results' => $countries, - ]; - - $sitesPayload = [ - 'meta' => [ - 'schema' => 'country_codes.v1', - 'source' => 'world-heritage-sites.json', - 'generated_at' => now()->toIso8601String(), - 'input' => $in, - 'input_files' => count($files), - 'input_rows_scanned' => $inputRows, - 'rows' => count($siteJudgements), - 'country_code_standard' => 'alpha-3', - 'null_means' => 'could_not_determine_country', - ], - 'results' => $siteJudgements, - ]; - - $exceptionsPayload = [ - 'meta' => [ - 'schema' => 'exceptions_country_codes.v1', - 'generated_at' => now()->toIso8601String(), - 'input' => $in, - 'input_files' => count($files), - 'input_rows_scanned' => $inputRows, - 'exceptions' => count($exceptions), - 'limit' => $exceptionsLimit, - ], - 'results' => $exceptions, - ]; - - $countriesJson = $this->encodeJson($countriesPayload, $pretty); - $sitesJson = $this->encodeJson($sitesPayload, $pretty); - $exceptionsJson = $this->encodeJson($exceptionsPayload, $pretty); - - if ($countriesJson === null || $sitesJson === null || $exceptionsJson === null) { - $this->error('Failed to encode output JSON'); - return self::FAILURE; - } - - if ($dryRun) { - $this->warn("[dry] would write: storage/app/{$outStoragePath}"); - $this->warn("[dry] would write: storage/app/{$sitesOutStoragePath}"); - $this->warn("[dry] would write: storage/app/{$exceptionsOutStoragePath}"); - return self::SUCCESS; - } - - Storage::disk('local')->put($outStoragePath, $countriesJson); - Storage::disk('local')->put($sitesOutStoragePath, $sitesJson); - Storage::disk('local')->put($exceptionsOutStoragePath, $exceptionsJson); - - $this->info("Wrote: storage/app/{$outStoragePath}"); - $this->info("Wrote: storage/app/{$sitesOutStoragePath}"); - $this->info("Wrote: storage/app/{$exceptionsOutStoragePath}"); - - return self::SUCCESS; - } - - private function upsertCountryRow(array &$countryMap, string $code, ?string $nameEn, array $existingJp, ?string $region): void - { - $code = strtoupper(trim($code)); - if ($code === '') { - return; - } - - if (!isset($countryMap[$code])) { - $countryMap[$code] = [ - 'state_party_code' => $code, - 'name_en' => ($nameEn !== null && trim($nameEn) !== '') ? $nameEn : $code, - 'name_jp' => $existingJp[$code] ?? null, - 'region' => $region, - ]; - return; - } - - if ($nameEn !== null) { - $nameEn = trim($nameEn); - if ($nameEn !== '') { - $current = (string)($countryMap[$code]['name_en'] ?? $code); - if ($current === $code) { - $countryMap[$code]['name_en'] = $nameEn; - } - } - } - - if (($countryMap[$code]['region'] ?? null) === null && $region !== null) { - $countryMap[$code]['region'] = $region; - } - - if (($countryMap[$code]['name_jp'] ?? null) === null && isset($existingJp[$code])) { - $countryMap[$code]['name_jp'] = $existingJp[$code]; - } - } - - private function normalizeRegionCode(mixed $v): ?string - { - if (!is_string($v)) { - return null; - } - - $code = strtoupper(trim($v)); - if ($code === '') { - return null; - } - - $allowed = ['EUR', 'AFR', 'APA', 'ARB', 'LAC']; - return in_array($code, $allowed, true) ? $code : null; - } - - private function collectJsonFiles(string $path): array - { - if (is_file($path)) { - return str_ends_with($path, '.json') ? [$path] : []; - } - - if (!is_dir($path)) { - return []; - } - - $files = []; - $rii = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS) - ); - - foreach ($rii as $file) { - if ($file->isFile() && str_ends_with($file->getFilename(), '.json')) { - $files[] = $file->getPathname(); - } - } - - sort($files); - return $files; - } - - private function resolvePath(string $path): string - { - $path = trim($path); - if ($path === '') { - return $path; - } - - if (str_starts_with($path, '/')) { - return $path; - } - if (preg_match('/^[A-Za-z]:\\\\/', $path) === 1) { - return $path; - } - - $storageCandidate = storage_path('app/' . ltrim($path, '/')); - if (file_exists($storageCandidate)) { - return $storageCandidate; - } - - return base_path($path); - } - - private function extractRows(array $json): ?array - { - if (array_key_exists('results', $json)) { - return is_array($json['results']) ? $json['results'] : null; - } - - return array_is_list($json) ? $json : null; - } - - private function readExistingCountriesJpMap(string $storageOutPath): array - { - if (!Storage::disk('local')->exists($storageOutPath)) { - return []; - } - - $raw = (string) Storage::disk('local')->get($storageOutPath); - $json = json_decode($raw, true); - if (!is_array($json)) { - return []; - } - - $rows = $this->extractRows($json); - if ($rows === null) { - return []; - } - - $map = []; - foreach ($rows as $row) { - if (!is_array($row)) { - continue; - } - $code = strtoupper(trim((string)($row['state_party_code'] ?? ''))); - if ($code === '') { - continue; - } - - $jp = $row['name_jp'] ?? null; - if (is_string($jp)) { - $jp = trim($jp); - } - if ($jp === '') { - $jp = null; - } - - if ($jp !== null) { - $map[$code] = $jp; - } - } - - ksort($map, SORT_STRING); - return $map; - } - - private function normalizeStringList(mixed $v): array - { - if (!is_array($v)) { - return []; - } - - $out = []; - foreach ($v as $x) { - if (!is_string($x)) { - continue; - } - $x = trim($x); - if ($x === '') { - continue; - } - $out[] = $x; - } - return $this->uniqueList($out); - } - - private function normalizeCodeList(mixed $v): array - { - $out = []; - - if (is_array($v)) { - foreach ($v as $x) { - if (!is_string($x)) { - continue; - } - $x = strtoupper(trim($x)); - if ($x !== '') { - $out[] = $x; - } - } - return $this->uniqueList($out); - } - - if (is_string($v)) { - $s = trim($v); - if ($s === '') { - return []; - } - - $parts = preg_split('/[,\|;\/\s]+/', $s) ?: []; - foreach ($parts as $p) { - $p = strtoupper(trim($p)); - if ($p !== '') { - $out[] = $p; - } - } - return $this->uniqueList($out); - } - - return []; - } - - private function uniqueList(array $list): array - { - $seen = []; - $out = []; - foreach ($list as $v) { - if (isset($seen[$v])) { - continue; - } - $seen[$v] = true; - $out[] = $v; - } - return $out; - } - - private function encodeJson(mixed $payload, bool $pretty): ?string - { - $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; - if ($pretty) { - $flags |= JSON_PRETTY_PRINT; - } - - $json = json_encode($payload, $flags); - return $json === false ? null : $json; - } -} diff --git a/src/app/Console/Commands/SplitWorldHeritageImageJson.php b/src/app/Console/Commands/SplitWorldHeritageImageJson.php deleted file mode 100644 index 690c9a2..0000000 --- a/src/app/Console/Commands/SplitWorldHeritageImageJson.php +++ /dev/null @@ -1,226 +0,0 @@ -option('in')); - $out = trim((string)$this->option('out')); - $pretty = (bool)$this->option('pretty'); - $dryRun = (bool)$this->option('dry-run'); - - if ($in === '') { - $this->error('Missing required option: --in'); - return self::FAILURE; - } - - $inPath = $this->resolvePathToFile($in); - if (!is_file($inPath)) { - $this->error("Input JSON not found: {$inPath}"); - return self::FAILURE; - } - - $raw = @file_get_contents($inPath); - if ($raw === false) { - $this->error("Failed to read input file: {$inPath}"); - return self::FAILURE; - } - - $json = json_decode($raw, true); - if (!is_array($json)) { - $this->error("Invalid JSON: {$inPath}"); - return self::FAILURE; - } - - $results = $json['results'] ?? null; - if (!is_array($results)) { - $this->error('Invalid raw format: expected {"results":[...]}'); - return self::FAILURE; - } - - $images = []; - $scanned = 0; - $skippedNoId = 0; - $skippedNoImages = 0; - - foreach ($results as $row) { - $scanned++; - if (!is_array($row)) { - $skippedNoId++; - continue; - } - - $idNoRaw = trim((string)($row['id_no'] ?? ($row['id'] ?? ''))); - if ($idNoRaw === '' || !is_numeric($idNoRaw)) { - $skippedNoId++; - continue; - } - $siteId = (int)$idNoRaw; - - $urls = $this->extractImageUrlsPreferImagesUrls($row); - if ($urls === []) { - $skippedNoImages++; - continue; - } - - foreach ($urls as $idx => $url) { - $images[] = [ - 'world_heritage_site_id' => $siteId, - 'url' => $url, - 'url_hash' => hash('sha256', $url), - 'sort_order' => $idx, - 'is_primary' => $idx === 0 ? 1 : 0, - ]; - } - } - - $payload = [ - 'meta' => [ - 'schema' => 'world_heritage_site_images.import.v1', - 'source_raw' => $in, - 'generated_at' => now()->toIso8601String(), - 'rows_scanned' => $scanned, - 'images' => count($images), - 'skipped_no_id' => $skippedNoId, - 'skipped_no_images' => $skippedNoImages, - 'target_table' => 'world_heritage_site_images', - 'rule' => 'prefer images_urls; fallback main_image_url.url', - ], - 'results' => $images, - ]; - - $encoded = $this->encodeJson($payload, $pretty); - if ($encoded === null) { - $this->error('Failed to encode output JSON'); - return self::FAILURE; - } - - $outPath = $this->resolvePathToFile($out); - - if ($dryRun) { - $this->info("[dry] would write: {$outPath}"); - $this->info("scanned={$scanned} images=" . count($images) . " skipped_no_id={$skippedNoId} skipped_no_images={$skippedNoImages}"); - return self::SUCCESS; - } - - $dir = dirname($outPath); - if (!is_dir($dir) && (!@mkdir($dir, 0777, true) && !is_dir($dir))) { - $this->error("Failed to create output dir: {$dir}"); - return self::FAILURE; - } - - if (@file_put_contents($outPath, $encoded) === false) { - $this->error("Failed to write: {$outPath}"); - return self::FAILURE; - } - - $this->info("Wrote {$outPath} (" . count($images) . " records)"); - $this->info("scanned={$scanned} skipped_no_id={$skippedNoId} skipped_no_images={$skippedNoImages}"); - return self::SUCCESS; - } - - private function extractImageUrlsPreferImagesUrls(array $row): array - { - $urls = []; - $images = $row['images_urls'] ?? null; - - if (is_array($images) && $images !== []) { - foreach ($images as $p) { - if (!is_string($p)) { - continue; - } - $p = trim($p); - if ($p !== '') { - $urls[] = $p; - } - } - } elseif (is_string($images)) { - $parts = preg_split('/\s*,\s*/', trim($images)) ?: []; - foreach ($parts as $p) { - $p = trim($p); - if ($p !== '') { - $urls[] = $p; - } - } - } - - if ($urls === []) { - $main = $row['main_image_url']['url'] ?? null; - if (is_string($main)) { - $main = trim($main); - if ($main !== '') { - $urls[] = $main; - } - } - } - - $seen = []; - $out = []; - - foreach ($urls as $u) { - if (isset($seen[$u])) { - continue; - } - $seen[$u] = true; - $out[] = $u; - } - - return $out; - } - - private function resolvePathToFile(string $path): string - { - $path = trim($path); - if ($path === '') { - return $path; - } - - if (str_starts_with($path, '/')) { - return $path; - } - if (preg_match('/^[A-Za-z]:\\\\/', $path) === 1) { - return $path; - } - - if (str_starts_with($path, 'storage/app/')) { - $path = substr($path, strlen('storage/app/')); - } - return storage_path('app/' . ltrim($path, '/')); - } - - private function encodeJson(mixed $payload, bool $pretty): ?string - { - $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; - if ($pretty) { - $flags |= JSON_PRETTY_PRINT; - } - - $json = json_encode($payload, $flags); - return $json === false ? null : $json; - } -} From 594937a1da3e735a071a75871c45ac2db130171f Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sat, 28 Mar 2026 15:38:53 +0900 Subject: [PATCH 2/3] fix: add study_region to WorldHeritageSeeder --- src/database/seeders/WorldHeritageSeeder.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/database/seeders/WorldHeritageSeeder.php b/src/database/seeders/WorldHeritageSeeder.php index 3df1cfa..e68ad2e 100644 --- a/src/database/seeders/WorldHeritageSeeder.php +++ b/src/database/seeders/WorldHeritageSeeder.php @@ -21,6 +21,7 @@ public function run(): void 'name_jp' => '姫路城', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', // ISO3 を持たせておく 'category' => 'Cultural', 'criteria' => json_encode(['i','iv']), @@ -44,6 +45,7 @@ public function run(): void 'name_jp' => '屋久島', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', 'category' => 'Natural', 'criteria' => json_encode(['vii','ix']), @@ -67,6 +69,7 @@ public function run(): void 'name_jp' => '白神山地', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', 'category' => 'Natural', 'criteria' => json_encode(['ix','x']), @@ -90,6 +93,7 @@ public function run(): void 'name_jp' => '古都京都の文化財', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', 'category' => 'Cultural', 'criteria' => json_encode(['ii','iv']), @@ -113,6 +117,7 @@ public function run(): void 'name_jp' => 'カルパティア山脈とヨーロッパ各地の古代及び原生ブナ林', 'country' => 'Slovakia', 'region' => 'Europe', + 'study_region' => 'Europe', 'state_party' => null, // 越境資産なので primary は pivot から出してる想定 'category' => 'Natural', 'criteria' => json_encode(['ix']), @@ -136,6 +141,7 @@ public function run(): void 'name_jp' => '紀伊山地の霊場と参詣道', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', 'category' => 'Cultural', 'criteria' => json_encode(['ii','iii','iv','vi']), @@ -159,6 +165,7 @@ public function run(): void 'name_jp' => '富士山—信仰の対象と芸術の源泉', 'country' => 'Japan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => 'JPN', 'category' => 'Cultural', 'criteria' => json_encode(['iii','vi']), @@ -182,6 +189,7 @@ public function run(): void 'name_jp' => 'シルクロード:長安-天山回廊の交易路網', 'country' => 'China', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => null, // primary は pivot CHN 'category' => 'Cultural', 'criteria' => json_encode(['ii','iii','vi']), @@ -205,6 +213,7 @@ public function run(): void 'name_jp' => 'シルクロード:ザラフシャン-カラクム回廊', 'country' => 'Tajikistan', 'region' => 'Asia', + 'study_region' => 'Asia', 'state_party' => null, 'category' => 'Cultural', 'criteria' => json_encode(['ii','iii']), From 1e05d87f204febb2ed50833fce8dfd3f4146bc81 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sat, 28 Mar 2026 15:39:12 +0900 Subject: [PATCH 3/3] fix: fallback region to study_region in getHeritageById --- .../WorldHeritageQueryService_getByIdTest.php | 4 ++-- .../Packages/Domains/WorldHeritageQueryService.php | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/app/Packages/Domains/Test/QueryService/WorldHeritageQueryService_getByIdTest.php b/src/app/Packages/Domains/Test/QueryService/WorldHeritageQueryService_getByIdTest.php index dd83156..3d1ae23 100644 --- a/src/app/Packages/Domains/Test/QueryService/WorldHeritageQueryService_getByIdTest.php +++ b/src/app/Packages/Domains/Test/QueryService/WorldHeritageQueryService_getByIdTest.php @@ -64,7 +64,7 @@ private function arrayData(): array 'name' => "Ancient and Primeval Beech Forests", 'heritage_name_jp' => "カルパティア山脈とヨーロッパ各地の古代及び原生ブナ林", 'country' => 'Slovakia', - 'region' => 'Europe', + 'study_region' => 'Europe', 'category' => 'Natural', 'criteria' => ['ix'], 'state_party' => null, @@ -149,7 +149,7 @@ public function test_check_data_value(): void $this->assertEquals($this->arrayData()['name'], $result->getName()); $this->assertEquals($this->arrayData()['heritage_name_jp'], $result->getHeritageNameJp()); $this->assertEquals($this->arrayData()['country'], $result->getCountry()); - $this->assertEquals($this->arrayData()['region'], $result->getRegion()); + $this->assertEquals($this->arrayData()['study_region'], $result->getRegion()); $this->assertEquals($this->arrayData()['category'], $result->getCategory()); $this->assertEquals($this->arrayData()['criteria'], $result->getCriteria()); $this->assertEquals($this->arrayData()['state_party'], $result->getStateParty()); diff --git a/src/app/Packages/Domains/WorldHeritageQueryService.php b/src/app/Packages/Domains/WorldHeritageQueryService.php index 67eb801..b2cbce9 100644 --- a/src/app/Packages/Domains/WorldHeritageQueryService.php +++ b/src/app/Packages/Domains/WorldHeritageQueryService.php @@ -170,7 +170,7 @@ public function getHeritageById(int $id): WorldHeritageDto 'heritage_name_jp' => $heritage->name_jp, 'country' => $displayCountry, 'country_name_jp' => $countryNameJp, - 'region' => $heritage->region, + 'region' => $heritage->study_region, 'category' => $heritage->category, 'year_inscribed' => $heritage->year_inscribed, 'latitude' => $heritage->latitude, @@ -199,6 +199,7 @@ public function getHeritagesByIds(array $ids, int $currentPage, int $perPage): P 'name_jp', 'country', 'region', + 'study_region', 'category', 'criteria', 'year_inscribed', @@ -222,15 +223,8 @@ public function getHeritagesByIds(array $ids, int $currentPage, int $perPage): P $thumbnailQuery->select([ 'images.id', 'images.world_heritage_id', - 'disk', 'path', - 'width', - 'height', - 'format', - 'checksum', 'sort_order', - 'alt', - 'credit', ]); }, ]) @@ -283,7 +277,7 @@ public function getHeritagesByIds(array $ids, int $currentPage, int $perPage): P 'name' => $heritage->name, 'name_jp' => $heritage->name_jp, 'country' => $heritage->country, - 'region' => $heritage->region, + 'region' => $heritage->study_region, 'category' => $heritage->category, 'criteria' => $heritage->criteria, 'state_party' => $statePartyName,