Atriumstatus

master9da7fa5

← all systems

Restaurant Growth Grader

Operationalhigh@atrium/grader

Lead-gen tool: scans a restaurant and returns a scored growth report.

checked 3m ago· owner Atrium

Checks

Grader page

The lead-gen funnel is closed.

100%

24h

100%

30d

GET https://atrium-grader.vercel.appexpects 200last 200 · 244ms · 3m ago

Search endpoint

Visitors cannot find their restaurant, so no scan can start.

100%

24h

100%

30d

GET https://atrium-grader.vercel.app/api/grader/searchexpects 405 or 400last 405 · 1959ms · 3m ago

What it is

A single page and six route handlers. A visitor searches for their restaurant, picks it from Google Autocomplete, and the scan fans out across four providers in parallel. The output is a scored report with an LLM-written narrative on top of it.

This is the only app in the estate that spends money per request — every scan costs Google Places calls plus one LLM completion — which makes its budget guard as important as its uptime.

What one scan actually does

POST /api/grader orchestrates:

  • Business data — Google Places v1: details, opening hours, categories.
  • Website — PageSpeed Insights (Lighthouse) plus a direct fetch of the restaurant's HTML.
  • Benchmark — Google searchNearby for comparable venues in the area.
  • Reputation — the Places reputation summary (rating, review count).
  • Social — ScrapeCreators, only when a profile is confidently detected. Skipped otherwise, and the report says so.
  • Narrative — one LLM completion that turns the numbers into prose.

Every step reports its own confidence and is recorded in the scan evidence, so a partial scan is still a usable report rather than a silent hole.

Outbound safety

The website scanner fetches URLs a stranger typed, so lib/safe-fetch.ts sits in front of it: http(s) only, private / loopback / link-local addresses rejected, response body size capped, hard timeout. This is the one place in the estate where SSRF is a live risk, and it is guarded deliberately.

Cost control — and why it is currently off

lib/providers/business-provider.ts counts Google Places calls against a daily and a monthly ceiling. Both ceilings come from env vars. Neither is set, so recordGooglePlacesUsage returns before it counts anything and the guard is a no-op.

Even once set, the counters live in process memory: they reset on every cold start and do not hold across serverless instances. Treat them as a speed bump, not a budget.

Rate limiting

middleware.ts limits per IP with an in-memory counter, with the same caveat — one instance, one counter. The file itself flags the fix: an Upstash-backed limiter before this runs multi-region. The status app already has an Upstash connection it could share.

Choosing the narrative model

GRADER_AI_PROVIDER selects the provider at request time: openrouter (default), google, or anthropic. Each needs its own key. Selecting anthropic today throws at model init because ANTHROPIC_API_KEY is not set anywhere — the narrative then returns null and the report renders without prose instead of failing loudly.

When it breaks

Symptom

Scans return but every report is missing its narrative

Check

GRADER_AI_PROVIDER and the matching key in the deployment environment.

Fix

Set the provider's key, or switch GRADER_AI_PROVIDER back to openrouter.

Symptom

Scans fail at the business-data step

Check

The Google Places monitor, then the Google Cloud console for quota or billing.

Fix

Restore quota. There is no fallback provider — despite the name, lib/open-data-places.ts is Google only.

Symptom

Unexpected Google bill

Check

GRADER_GOOGLE_DAILY_LIMIT and GRADER_GOOGLE_MONTHLY_LIMIT — if unset, nothing was ever capped.

Fix

Set both, and move the counters to Upstash so they survive restarts.

Symptom

Social section always empty

Check

SCRAPECREATORS_API_KEY; the social route 503s without it.

Fix

Set the key. The report is designed to degrade here, so this fails quietly by design.

Needs

  • Google Places API v1Every piece of business data in a grader scan — and the only paid call per scan.
  • PageSpeed InsightsLighthouse scores for the website half of a grader scan.
  • ScrapeCreatorsPublic social profile data for the optional social step of a grader scan.
  • LLM providersOpenRouter, Anthropic or Google — whichever writes the grader's narrative.

Configuration

variablestatuspurpose
GOOGLE_PLACES_API_KEYapps/atrium.grader/.envsetAuth for every Google Places call.
PAGESPEED_API_KEYapps/atrium.grader/.envsetGates and authenticates the PageSpeed audit.
SCRAPECREATORS_API_KEYapps/atrium.grader/.envsetAuth for social scraping.
OPENROUTER_API_KEYapps/atrium.grader/.envsetDefault narrative provider.
GOOGLE_GENERATIVE_AI_API_KEYapps/atrium.grader/.envsetNarrative provider when GRADER_AI_PROVIDER=google.
ANTHROPIC_API_KEYread at lib/report-agent.ts:156missingNarrative provider when GRADER_AI_PROVIDER=anthropic.In neither .env nor .env.example. Selecting anthropic throws at model init and the narrative silently returns null.
GRADER_GOOGLE_DAILY_LIMITapps/atrium.grader/.env.examplemissingPer-day cap on Google Places calls.Unset, so the spend guard never counts. Both limits must be set for it to engage.
GRADER_GOOGLE_MONTHLY_LIMITapps/atrium.grader/.env.examplemissingPer-month cap on Google Places calls.
GRADER_BUSINESS_PROVIDERapps/atrium.grader/.envdeadIntended osm|google|auto switch.No code reads it. Setting it changes nothing.
WEBSITE_AUDIT_PROVIDERapps/atrium.grader/.envdeadIntended basic|pagespeed switch.No code reads it.

Where to start reading

  • Scan orchestration

    apps/atrium.grader/app/api/grader/route.ts
  • Google Places client

    apps/atrium.grader/lib/google-places-client.ts
  • Narrative model selection

    apps/atrium.grader/lib/report-agent.ts
  • SSRF guard

    apps/atrium.grader/lib/safe-fetch.ts
  • Spend guard

    apps/atrium.grader/lib/providers/business-provider.ts

In the repo

path

apps/atrium.grader

version

0.0.1

files

48 (11 tests)

lines

5,717

routes

8

depends on

@atrium/application, @atrium/ui
engineering view →

Incident history

No incidents recorded for this system.