* } */ public function parse(string $text): array { $text = str_replace("\r\n", "\n", $text); $text = str_replace("\r", "\n", $text); // Cliente $cliente = $this->firstMatch($text, '/(?:Codice\s+Cliente|Cod\.\s*Cliente|Cliente\s*n\.|Client\s*code)\s*:?\s*([A-Z0-9_]{4,})/i'); // Contratto $contratto = $this->firstMatch($text, '/(?:Contratto|Cod\.\s*Contratto|Codice\s+Contratto|Contract\s*n\.)\s*:?\s*([A-Z0-9_]{4,})/i'); // PDR / POD $pdr = $this->firstMatch($text, '/\bPDR\b\s*:?\s*([0-9]{14})\b/i'); $pod = $this->firstMatch($text, '/\bPOD\b\s*:?\s*([A-Z0-9]{14,15})\b/i'); // Scadenza $scadenza = $this->toIsoDate($this->firstMatch($text, '/(?:Scadenza|Entro\s+il|Data\s+scadenza)\s*:?\s*(\d{2}\/\d{2}\/\d{4})/i')); // Cerca consumi del tipo: "Consumo Fatturato APRILE 2026 910 Smc" // O genericamente "Consumo... [valore] Smc/kWh" $consumi = []; if (preg_match_all('/(?:Consumo\s+Fatturato|Consumo|Consumo\s+rilevato|Consumo\s+stimato)\s+(?:([A-Z]+)\s+(\d{4})\s+)?(\d+(?:[\.,]\d+)?)\s*(Smc|kWh)/i', $text, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $monthName = !empty($m[1]) ? strtoupper(trim($m[1])) : null; $yearVal = !empty($m[2]) ? (int) $m[2] : null; $valore = $this->toFloatIt($m[3]); $unita = trim($m[4]); $dal = null; $al = null; if ($monthName && $yearVal) { $months = [ 'GENNAIO' => 1, 'FEBBRAIO' => 2, 'MARZO' => 3, 'APRILE' => 4, 'MAGGIO' => 5, 'GIUGNO' => 6, 'LUGLIO' => 7, 'AGOSTO' => 8, 'SETTEMBRE' => 9, 'OTTOBRE' => 10, 'NOVEMBRE' => 11, 'DICEMBRE' => 12, 'JANUARY' => 1, 'FEBRUARY' => 2, 'MARCH' => 3, 'APRIL' => 4, 'MAY' => 5, 'JUNE' => 6, 'JULY' => 7, 'AUGUST' => 8, 'SEPTEMBER' => 9, 'OCTOBER' => 10, 'NOVEMBER' => 11, 'DECEMBER' => 12 ]; $mNum = $months[$monthName] ?? null; if ($mNum) { $dal = Carbon::create($yearVal, $mNum, 1)->startOfMonth()->format('Y-m-d'); $al = Carbon::create($yearVal, $mNum, 1)->endOfMonth()->format('Y-m-d'); } } $consumi[] = [ 'dal' => $dal, 'al' => $al, 'valore' => $valore, 'unita' => $unita, 'tipologia_lettura' => 'fatturata', ]; } } // Se non troviamo date dai consumi ma c'รจ un intervallo di date nel testo, tipo "dal 01/04/2026 al 30/04/2026" if (count($consumi) === 0 && preg_match('/dal\s+(\d{2}\/\d{2}\/\d{4})\s+al\s+(\d{2}\/\d{2}\/\d{4})/i', $text, $mDates)) { $dal = $this->toIsoDate($mDates[1]); $al = $this->toIsoDate($mDates[2]); // Prova a estrarre il valore di consumo vicino a Smc o kWh $val = null; $uni = 'Smc'; if (preg_match('/(\d+(?:[\.,]\d+)?)\s*(Smc|kWh)/i', $text, $mVal)) { $val = $this->toFloatIt($mVal[1]); $uni = $mVal[2]; } if ($val !== null) { $consumi[] = [ 'dal' => $dal, 'al' => $al, 'valore' => $val, 'unita' => $uni, 'tipologia_lettura' => 'rilevata', ]; } } return [ 'codici' => [ 'cliente' => $cliente, 'contratto' => $contratto, 'pdr' => $pdr, 'pod' => $pod, ], 'pagamento' => [ 'scadenza' => $scadenza, ], 'consumi' => $consumi, ]; } private function firstMatch(string $text, string $pattern): ?string { if (preg_match($pattern, $text, $matches)) { return trim($matches[1]); } return null; } private function toFloatIt(mixed $val): ?float { if ($val === null) return null; $val = str_replace('.', '', $val); $val = str_replace(',', '.', $val); return is_numeric($val) ? (float) $val : null; } private function toIsoDate(?string $value): ?string { if (! $value) { return null; } try { return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d'); } catch (\Throwable) { return null; } } }