netgescon-day0/app/Console/Commands/IstatSyncIndiciCommand.php

179 lines
6.0 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\IstatIndiceMensile;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
class IstatSyncIndiciCommand extends Command
{
protected $signature = 'istat:sync-indici
{--url= : URL JSON sorgente}
{--file= : File JSON locale}
{--series=FOI : Codice serie ISTAT (es. FOI)}
{--source=ISTAT API : Nome fonte}
{--dry-run : Mostra righe parsate senza salvare}';
protected $description = 'Sincronizza indici ISTAT mensili da URL o file JSON';
public function handle(): int
{
$series = strtoupper((string) $this->option('series'));
$source = (string) $this->option('source');
$dryRun = (bool) $this->option('dry-run');
$url = trim((string) ($this->option('url') ?: config('services.istat.api_url', '')));
$file = trim((string) ($this->option('file') ?: ''));
if ($url === '' && $file === '') {
$this->error('Specifica --url oppure --file (oppure configura ISTAT_API_URL).');
return self::FAILURE;
}
try {
$payload = $this->loadPayload($url, $file);
$rows = $this->extractRows($payload);
if (empty($rows)) {
$this->warn('Nessuna riga valida trovata nella sorgente.');
return self::SUCCESS;
}
$upserts = [];
foreach ($rows as $row) {
$normalized = $this->normalizeRow($row);
if (! $normalized) {
continue;
}
$upserts[] = [
'codice_serie' => $series,
'anno' => $normalized['anno'],
'mese' => $normalized['mese'],
'indice' => $normalized['indice'],
'fonte' => $source,
'data_pubblicazione' => $normalized['data_pubblicazione'],
'payload' => $row,
'sincronizzato_il' => now(),
'created_at' => now(),
'updated_at' => now(),
];
}
if (empty($upserts)) {
$this->warn('Righe presenti ma nessuna convertibile in (anno,mese,indice).');
return self::SUCCESS;
}
if ($dryRun) {
$this->info('Dry-run: ' . count($upserts) . ' righe parsate.');
$this->table(['serie', 'anno', 'mese', 'indice'], collect($upserts)->take(20)->map(fn($r) => [
$r['codice_serie'],
$r['anno'],
$r['mese'],
$r['indice'],
])->all());
return self::SUCCESS;
}
IstatIndiceMensile::query()->upsert(
$upserts,
['codice_serie', 'anno', 'mese'],
['indice', 'fonte', 'data_pubblicazione', 'payload', 'sincronizzato_il', 'updated_at']
);
$this->info('Sincronizzazione completata: ' . count($upserts) . ' righe upsertate.');
return self::SUCCESS;
} catch (\Throwable $e) {
$this->error('Errore sincronizzazione ISTAT: ' . $e->getMessage());
return self::FAILURE;
}
}
private function loadPayload(string $url, string $file): array
{
if ($file !== '') {
if (! is_file($file)) {
throw new \RuntimeException('File non trovato: ' . $file);
}
$decoded = json_decode((string) file_get_contents($file), true);
if (! is_array($decoded)) {
throw new \RuntimeException('JSON file non valido.');
}
return $decoded;
}
$timeout = (int) config('services.istat.timeout', 30);
$resp = Http::timeout(max(5, $timeout))->get($url);
if (! $resp->successful()) {
throw new \RuntimeException('HTTP ' . $resp->status() . ' da sorgente ISTAT.');
}
$decoded = $resp->json();
if (! is_array($decoded)) {
throw new \RuntimeException('Risposta JSON non valida.');
}
return $decoded;
}
private function extractRows(array $payload): array
{
if (isset($payload[0]) && is_array($payload[0])) {
return $payload;
}
foreach (['data', 'rows', 'values', 'result'] as $k) {
$v = Arr::get($payload, $k);
if (is_array($v) && isset($v[0]) && is_array($v[0])) {
return $v;
}
}
return [];
}
private function normalizeRow(array $row): ?array
{
$anno = $row['anno'] ?? $row['year'] ?? null;
$mese = $row['mese'] ?? $row['month'] ?? null;
$indice = $row['indice'] ?? $row['value'] ?? $row['valore'] ?? null;
$period = (string) ($row['periodo'] ?? $row['period'] ?? '');
if (($anno === null || $mese === null) && preg_match('/^(\d{4})[-\/](\d{1,2})$/', $period, $m)) {
$anno = (int) $m[1];
$mese = (int) $m[2];
}
if (! is_numeric($anno) || ! is_numeric($mese) || ! is_numeric($indice)) {
return null;
}
$anno = (int) $anno;
$mese = (int) $mese;
$indice = (float) $indice;
if ($anno < 1900 || $anno > 2300 || $mese < 1 || $mese > 12) {
return null;
}
$pub = $row['data_pubblicazione'] ?? $row['published_at'] ?? null;
$pubDate = null;
if (is_string($pub) && trim($pub) !== '') {
try {
$pubDate = Carbon::parse($pub)->format('Y-m-d');
} catch (\Throwable) {
$pubDate = null;
}
}
return [
'anno' => $anno,
'mese' => $mese,
'indice' => $indice,
'data_pubblicazione' => $pubDate,
];
}
}