'float', 'lon' => 'float', ]; /** * find an existing location by its normalized components or create it. * backfills lat/lon/raw_address/place_name if the found row is missing them. */ public static function findOrCreateNormalized(array $norm, ?string $raw = null): self { $lookup = [ 'display_name' => $norm['display_name'] ?? null, 'street' => $norm['street'] ?? null, 'city' => $norm['city'] ?? null, 'state' => $norm['state'] ?? null, 'postal' => $norm['postal'] ?? null, 'country' => $norm['country'] ?? null, ]; $existing = static::where($lookup)->first(); // fallback: try matching by the raw label (useful for seeds like "Home") if (!$existing && $raw) { $existing = static::where('display_name', $raw) ->orWhere('raw_address', $raw) ->first(); } if ($existing) { $changed = false; // backfill coords if missing (and we have them) $hasNewCoords = (!empty($norm['lat']) || !empty($norm['lon'])); if (($existing->lat === null || $existing->lon === null) && $hasNewCoords) { $existing->lat = $norm['lat'] ?? $existing->lat; $existing->lon = $norm['lon'] ?? $existing->lon; $changed = true; } // backfill raw address if missing if ($raw && $existing->raw_address === null) { $existing->raw_address = $raw; $changed = true; } // backfill place_name if missing if ($existing->place_name === null && !empty($norm['place_name'])) { $existing->place_name = $norm['place_name']; $changed = true; } if ($changed) { $existing->save(); } return $existing; } return static::create([ 'display_name' => $norm['display_name'] ?? ($raw ?: 'Unknown'), 'place_name' => $norm['place_name'] ?? null, 'raw_address' => $norm['raw_address'] ?? $raw, 'street' => $norm['street'] ?? null, 'city' => $norm['city'] ?? null, 'state' => $norm['state'] ?? null, 'postal' => $norm['postal'] ?? null, 'country' => $norm['country'] ?? null, 'lat' => $norm['lat'] ?? null, 'lon' => $norm['lon'] ?? null, ]); } /** * thin alias for backwards compatibility with older call-sites. * prefer using findOrCreateNormalized() directly. */ public static function firstOrCreateNormalized(array $norm, ?string $raw = null): self { return static::findOrCreateNormalized($norm, $raw); } /** * create/find a label-only location (no geocode) */ public static function labelOnly(string $label): self { return static::firstOrCreate( ['display_name' => $label], ['raw_address' => null] ); } /** * accessor: short label for ui (falls back gracefully) */ public function getShortLabelAttribute(): string { return $this->place_name ?? $this->display_name ?? trim(collect([$this->street, $this->city])->filter()->join(', ')); } }