From food-review-review
Audits and ranks restaurants by genuine food quality by reading every Google/TripAdvisor review via Apify, filtering non-food and unsophisticated-palate complaints, and producing a visual ranked HTML report.
How this skill is triggered — by the user, by Claude, or both
Slash command
/food-review-review:food-review-review <explicit restaurant list> | "<cuisine> restaurants in <place>"<explicit restaurant list> | "<cuisine> restaurants in <place>"The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Find the **best places to actually eat** in a set, judged on **real food quality and
Find the best places to actually eat in a set, judged on real food quality and authenticity — not on price, speed, or the opinions of people who don't enjoy authentic world food. The output is a ranked, visual report.
The audience is well-travelled, culinarily diverse diners who know authentic world food and are happy to travel or pay for it. Therefore:
Every review with text is placed in exactly ONE cluster. These are cuisine-agnostic; the examples just illustrate.
| Cluster | Meaning | Verdict |
|---|---|---|
non_food_complaint | Substance is ONLY non-food: delivery time, lateness, waiting, price/value, wrong or missing order, packaging, parking, staff rudeness/service — no judgement of how the food tasted. | Discount |
unsophisticated_complaint | Negative about the FOOD but from a palate unaccustomed to authentic world cuisine: judges by anglicised/Westernised staples (chips, chicken balls, "curry sauce"), calls properly cooked food "bland/no flavour/not spicy enough", expects MSG or heavy sweet sauce, objects to authentic texture/spicing, vague "greasy" with no specifics. | Discount |
mixed_or_other | Genuinely mixed (good food + a non-food gripe) or impossible to place. | Set aside |
credible_food_complaint | Negative about the food citing a concrete, legitimate defect OR from a clearly discerning diner: raw/undercooked, made-ill, foreign object, hygiene rating, "the [regional dish] should be made with X not Y", "every dish tastes the same", a proper assessment of a technique. | Keep — real signal |
generic_praise | Positive but no authenticity signal: "lovely", "best in town", "hot and tasty", great portions/value/service, praises anglicised staples. | Positive, low weight |
authentic_praise | Positive AND food-literate: names traditional/regional/off-menu dishes and judges them on their own terms; recognises proper recipes vs cheap shortcuts; orders the things a knowledgeable diner of that cuisine would. | Trust — strong signal |
rating_only | A star rating with NO written text. No content to judge. | Separate bucket (counted for volume only) |
The ranking score rewards authentic_praise (×2) and generic_praise (×1), penalises
credible_food_complaint (×2) and lightly unsophisticated_complaint (×0.5); non_food,
rating_only, and mixed are excluded. (The bundled report computes and displays this.)
⚠️ THIS WORKFLOW WILL BE VERY TOKEN EXPENSIVE DUE TO LARGE NUMBER OF REVIEWS
(<N> reviews → roughly <N/12> classifier agents). Cancel now if you don't want this.
Reviews are harvested with Apify scrapers via its MCP server. Google Maps hides reviews behind a cookie-consent wall that defeats plain fetching, and TripAdvisor paginates awkwardly — Apify solves both and returns clean structured data with real timestamps.
Check if it's already connected: run claude mcp list (look for apify ... ✔ Connected) or
search for the tools (mcp__apify__call-actor, mcp__apify__get-dataset-items). If connected,
skip ahead.
If not connected, walk the user through this (it's quick and cheap — free tier covers normal use; the Google reviews scraper is ~$0.0006/review):
apify_api_…).claude mcp add --transport http apify https://mcp.apify.com --header "Authorization: Bearer apify_api_YOUR_TOKEN"
(Alternatively claude mcp add --transport http apify https://mcp.apify.com then /mcp →
select apify → complete the browser OAuth.)claude --continue. A running session does
not hot-load a newly added MCP server — this restart is required, or the tools won't appear.claude mcp list → apify … ✔ Connected, then re-invoke this skill.$ARGUMENTS)mcp__apify__call-actor with actor compass/crawler-google-places, input
{ "searchStringsArray": ["Indian restaurant Norwich"], "maxCrawledPlaces": 30, "language": "en" },
then read the dataset (mcp__apify__get-dataset-items) and keep the genuine matches of that
cuisine (use title, categoryName, reviewsCount, url/placeId, totalScore). Present
the resolved list (name, area, Google review count) and proceed.Record for each outlet: a clean name, its Google Maps place URL or placeId, and (if found) its TripAdvisor review-page URL.
Always mcp__apify__fetch-actor-details (output {inputSchema:true,pricing:true}) before running
an actor, then call-actor with waitSecs:0 (fire-and-forget) and poll with
get-actor-run (waitSecs:45) until SUCCEEDED.
Google — actor compass/google-maps-reviews-scraper (verify via search-actors "Google Maps reviews"):
{ "startUrls": [{"url":"https://www.google.com/maps/search/<Name+Full+Address>"}, ...],
"maxReviews": 1000, "reviewsSort": "newest", "language": "en",
"reviewsOrigin": "google", "personalData": true }
reviewsOrigin:"google" keeps Google-native reviews only (so they don't duplicate TripAdvisor).
Pass one startUrl per outlet; a /maps/search/ URL with the full name + street + town +
postcode resolves to the right place. After it finishes, verify each place via a compact
get-dataset-items projection (title,address,city,postalCode,reviewsCount,totalScore) — confirm
the address/town matches and that scraped count ≈ reviewsCount.
TripAdvisor — find the actor with search-actors "Tripadvisor reviews" (e.g.
maxcopell/tripadvisor-reviews), read its input schema, and run it with each outlet's TripAdvisor
URL (or let it search by name+location), pulling all reviews. If no TripAdvisor actor is
available, fall back to WebFetch on the TripAdvisor review pages, paginating with the -orN-
offset in the URL (e.g. …-Reviews-or10-…, -or20-, …) and de-duplicating.
Get the data onto disk. get-dataset-items results that exceed the token cap are written to a
file automatically (the tool tells you the path) — that's the easiest route. Otherwise project
fields and page through. You need at least: review text, stars/rating, publish date
(for the year), reviewer name, and which outlet + source it belongs to.
Write a small Python script (in the scratchpad) that loads the harvested files and produces, for
every review, a record: {id, restaurant, reviewer, rating(1-5), year(int), title, text, source}.
Map each Google place title/placeId to its outlet name; derive year from the publish
timestamp (convert any relative dates against today). De-duplicate by review id / (name+text).
batch_000.json …), each an array of
{id, restaurant, rating, title, text}.rating_only.json list (classified deterministically, not by an
agent).Print the total and the Step-1 §HARD RULE 2 warning if > 500.
Use the Workflow tool. One agent per batch file: it Reads the file and classifies each
review against the rubric, returning structured output. Use the session model at high effort
— do not pass a cheaper model. (Implementation note: pass the batch-file paths by embedding
them as a literal array in the workflow script; the args global has been unreliable.)
Give each classifier agent the exact rubric from "The taxonomy" above, and this schema per
review: {id, cluster (the 6 content clusters), sentiment (positive|negative|mixed), authenticity_signal (literate|anglicised|neutral), dishes_mentioned (string[]), rationale}.
Sketch:
const paths = [ "/abs/batch_000.json", /* …embed every batch path here… */ ];
const RUBRIC = `…the taxonomy text, verbatim…`;
const CLASSIFY_SCHEMA = { /* object with classifications: [{id,cluster,sentiment,authenticity_signal,dishes_mentioned,rationale}] */ };
phase('Classify');
const results = await parallel(paths.map((p,i) => () =>
agent(`${RUBRIC}\nRead the JSON file at:\n${p}\nIt is an array of reviews (id, restaurant, rating, title, text). Classify EVERY one, keyed by id.`,
{ label:`classify:${i}`, phase:'Classify', agentType:'general-purpose', effort:'high', schema:CLASSIFY_SCHEMA })
.then(r => (r && r.classifications) || []) ));
return { classifications: results.filter(Boolean).flat() };
For categories with many outlets you may have a separate harvest workflow too, but classification is the part that MUST fan out one-agent-reads-each.
Join the classifications back to the review records by id. Add the rating_only reviews
(cluster rating_only; sentiment from stars: ≥4 positive, 3 mixed, ≤2 negative). Then compute:
counts.byCluster, counts.byRestaurant (name → cluster → n), counts.bySourcesummary: dismissible (= non_food + unsophisticated), credible_negative, positive
(= authentic + generic), rating_only, mixedhistogram: years (sorted), overall (year → cluster → n), byRestaurant (name → year → cluster → n)restaurants[]: {name, score_label (e.g. "Google 4.5★"), total, badge, take} — you write
the one-line badge and 1–2-sentence take per outlet from its cluster mix + notable quotes.quotes[]: ~6–8 hand-picked verbatim examples per pile (authentic_praise, credible_food_complaint,
and the discountable non_food/unsophisticated), each {reviewer, restaurant, year, rating, text, cluster, source}. Truncate text to ~320 chars.meta: {scope (e.g. "Indian restaurants in Norwich"), total, date, sources (labels)}Write it all to combined.json in the schema documented at the top of scripts/build_report.py.
Run the bundled generator (vertical layout + ranked leaderboard, scales to any number of
outlets). It ships alongside this skill at scripts/build_report.py — resolve that path relative
to this SKILL.md's own directory (the skill's plugin install location):
python3 <this-skill-dir>/scripts/build_report.py <combined.json> <out.html>
Then publish with the Artifact tool (favicon a food emoji, e.g. 🍜/🥡/🍛). Give the user the URL plus a tight prose summary: the ranked top picks, the totals (dismissible / credible / positive / star-only), and any surprises (e.g. an outlet that looks great on stars but hides a food-safety pattern). Always restate the caveats: classification is automated ("~this many"), and star-only ratings are bucketed separately.
npx claudepluginhub samthebest/dump --plugin food-review-reviewAudits restaurants for seed-oil-free and organic options by reading websites, menus, and reviews with parallel agents. Produces a ranked HTML report with verdicts and reliability scores.
Audits a restaurant's local-pack rank, review velocity/themes, and TikTok buzz for diner queries. Useful for 'are we in the map pack' or 'what are people saying about us' questions.
Scrapes reviews, ratings, and brand mentions from Google Maps, Booking.com, TripAdvisor, Facebook, Instagram, YouTube, and TikTok using Apify Actors.