179 lines
6.0 KiB
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,
|
|
];
|
|
}
|
|
}
|