@extends('admin.layout', ['menuKey' => 'crawler']) @section('title', 'Crawler') @section('content') @php $hbAt = isset($heartbeat['at']) ? \Illuminate\Support\Carbon::parse($heartbeat['at']) : null; $hbOk = $hbAt && $hbAt->gt(now()->subMinutes(15)); $pill = fn ($s) => ['active' => 'on', 'paused' => 'wait', 'failing' => 'bad', 'blocked' => 'bad', 'robots' => 'off', 'opted_out' => 'off', 'existing_org' => 'off'][$s] ?? 'off'; @endphp
Healthcare organizations whose own careers sites the crawler visits. Their open jobs go on the board as Unclaimed posts, rewritten in new words with the same facts, with an Apply button to the employer's site. Each site is visited in full every {{ config('sync.revisit_days') }} days and each live job is checked daily; a job comes down after two misses in a row.
Each facility's website is looked up with Claude and web search (${{ number_format($finder['spent'], 2) }} of ${{ $finder['budget'] }} this month, {{ number_format($finder['calls']) }} lookups). The crawler then checks the site shows the facility's phone or street address before crawling it. Lists refresh on the 3rd of each month.
@if ($finderPaused)No list downloaded yet. Tick a list above and save; it downloads within a minute.
@else| List | @foreach (\App\Models\DirectoryEntry::STATUSES as $k => $label){{ $label }} | @endforeach
|---|---|
| {{ config('sync.directory.datasets.'.$set.'.label', $set) }} | @foreach (array_keys(\App\Models\DirectoryEntry::STATUSES) as $k){{ number_format($counts[$k] ?? 0) }} | @endforeach
| Facility | Website | Result |
|---|---|---|
| {{ $d->name }} {{ $d->city }}, {{ $d->state }} |
@if ($d->website){{ $d->domain }} ↗@else – @endif | {{ \App\Models\DirectoryEntry::STATUSES[$d->status] ?? $d->status }}@if ($d->reason) {{ $d->reason }}@endif |
| Organization | Status | Found with | Live | Fix | Last visit | Next | |
|---|---|---|---|---|---|---|---|
| {{ $s->name }} {{ $s->domain }}@if ($s->organization_id) · org #{{ $s->organization_id }}@endif @if ($s->careers_url) careers page ↗@endif |
{{ $s->statusLabel() }}@if ($s->status_reason) {{ $s->status_reason }}@endif |
{{ $s->detected ?? '–' }}@if ($s->board) {{ \Illuminate\Support\Str::limit($s->board, 30) }}@endif |
{{ $s->live_count }} | {{ $s->attn_count ?: '' }} | {{ $s->last_visit_at?->diffForHumans() ?? 'never' }} | {{ $s->status === 'active' ? ($s->next_visit_at?->isPast() ? 'now' : $s->next_visit_at?->diffForHumans()) : '–' }} | @if ($s->status !== 'opted_out') @endif |
Real jobs that can't go up as they are: no pay where the law requires it, a title that matches none of the standard titles, no location. Most fix themselves when the employer updates their page. Add a missing job title in Job titles (with the employer's wording as an alias), then retry.
@if ($attn->isEmpty())Nothing waiting.
@else| Job | Why | |
|---|---|---|
| {{ \Illuminate\Support\Str::limit((string) ($i->raw['title'] ?? $i->external_id), 70) }} ↗ {{ $i->source?->name }} |
{{ $i->reason }} |
No visits yet.
@else| When | Site | Crawler found | Result |
|---|---|---|---|
| {{ $r->created_at->format('M j, g:i A') }} | {{ $sourceNames[$r->sync_source_id] ?? '#'.$r->sync_source_id }} | {{ $r->status }} | {{ $r->result ?? 'Processing…' }} |