Skip to main content

The local prompt corpus

Two people measure whether an AI engine recommends a plumber in Leeds. One asks "best plumber Leeds". The other asks "I need a plumber today in Leeds — who can actually come out?". They get different businesses, publish different numbers, and neither result can be checked against the other, because neither published the question.

This page is the fix: a fixed, numbered, versioned set of local query templates, with the rules for filling them in and the rules for what you are allowed to conclude. Copy it, fork it, cite the version you used. It is meant to be reused by people who have never heard of us, and it works with any engine, any tool, and a spreadsheet.

Corpus version 1.0 · published 2026-07-27. Nothing on this page is a measurement. It is the instrument.

Why a shared corpus is worth having

An AI-visibility number is a rate over runs of a question. Change the question and you have changed the instrument, which means:

  • Two businesses are only comparable if they were asked the same thing. "Our client is mentioned 4/5 and the market average is 2/5" is meaningless unless both sides ran the same templates.
  • Two dates are only comparable if the question did not move. A prompt edit resets the series (LSM-AI-14). If you cannot say which version of the question produced last quarter's number, you do not have a trend.
  • A published method can be attacked, which is the point. A corpus with IDs is falsifiable. Somebody can tell you row C-09 is badly worded, and you can fix C-09 in v1.1 and say so.

The four intents below — discovery, comparison, trust, logistics — are the manual's taxonomy, not Google's; Google publishes no local query-intent classification. They earn their place because each is answered by a different surface and moved by a different lever. The long argument is in what people actually search.

The shape of a row

Every row is a template with slots. Fill the slots, wrap it in the carrier prompt, run it.

SlotMeansExample fill
{category}What a customer calls the business, not what the trade calls itplumber, coffee shop, dentist
{service}One specific job inside that categoryboiler repair, root canal, tax return
{city}The town or city, as a customer would say itLeeds, Bristol
{area}A neighbourhood, district or landmarkShoreditch, the city centre
{lat} {lng}Decimal coordinates of the point you are asking from53.7965, -1.5478
{constraint}A qualifier a customer would actually say aloudopen on Sunday, that takes walk-ins, wheelchair accessible
{audience}Who it is forwith young kids, for a nervous patient
{brand}The business name — branded rows onlyAcme Plumbing
{rival}A named competitor — branded rows onlyRiverside Plumbing

Three columns qualify each row.

Register — how the question is phrased, which matters because a search box and an assistant get asked differently.

RegisterShapeWhere it belongs
term2–5 words, keyword-shapedRank tracking; still valid as a probe
spokenA short question a person would say out loudBoth
conversationalA full sentence carrying context or a constraintProbes; useless as a tracked keyword

The register ladder exists because assistants are asked longer questions than search boxes are, so a probe set made only of term rows under-samples the surface.

Whether register changes which businesses get named is not established here (inference — the mechanism is that a longer prompt gives the model more retrieval material; we have run no controlled register test). The probe that would settle it is at the end of this page.

Rank — is this row worth a slot in a rank tracker? yes / weak / no.

Probe — is this row valid as an AI-visibility probe? yes / flagged (valid but must be kept in its own series) / audit (run it, but never pool it into a visibility rate) / no.

The carrier prompt

A template is not a probe until it is geo-anchored and instructed. Assistant APIs have no location parameter — the coordinate can only go in the prose (LSM-AI-11). This is the carrier, reproduced verbatim so that a hand-run probe and a tooled one are the same measurement:

Someone near latitude {lat}, longitude {lng} asks: "{filled template}".
Recommend the specific local businesses that best answer this, by name, citing your sources.

Each clause is load-bearing:

  • Someone near latitude… — third person, so the model is answering about a place rather than about you. The coordinate is a hint the model may honour, ignore or reinterpret; it is not a constraint on retrieval.
  • "{filled template}" — quoted, so the customer's phrasing survives intact instead of being absorbed into your instructions.
  • the specific local businesses… by name — without it, assistants routinely answer with criteria and advice, and there is nothing to extract.
  • citing your sources — without it, several engines return no source list at all, which makes the citation axis unmeasurable for that run rather than negative (LSM-AI-20).

Two things that will quietly ruin a run:

Do not append "answer only in JSON". On a grounded Gemini call it stops the search tool engaging: you get well-formed JSON, often a negative result, and zero grounding metadata (LSM-AI-28). Ask in natural language; convert to JSON in a second, ungrounded call.

Do not edit the carrier mid-series. A wording change is an instrument change. If you must edit it, bump your corpus version, note the date, and start a new series.

Corpus v1.0

Forty-six templates. IDs are permanent: D-07 will always mean what it means here, and a retired row is marked retired rather than reused.

Discovery — someone needs the category and has no name in mind

Unbranded by construction. This is what the map pack exists to answer, and it is where most people's entire tracked set lives.

IDTemplateRegisterRankProbe
D-01{category}termyesyes
D-02{category} near metermyesno — the carrier already states the coordinate, so this is D-01 billed twice
D-03{category} in {city}termyesyes
D-04{category} {area}termyesyes
D-05{service} {city}termyesyes
D-06emergency {category} {city}termyesyes
D-0724 hour {category} near {area}termyesflagged — temporal
D-08{category} open now {city}termweakaudit — the engine's "now" is not the searcher's
D-09affordable {category} in {city}termyesyes
D-10{category} that {constraint} in {city}termweakyes
D-11where can I get {service} near {area}?spokennoyes
D-12who does {service} around {area}?spokennoyes
D-13I need a {category} today in {city} — who can actually come out?conversationalnoyes
D-14I've just moved to {area} and need a {category}. Where do people go?conversationalnoyes
D-15is there a {category} in {area} that does {service}?conversationalnoyes
D-16{category} in {city} {audience}termweakyes

D-02 is in the corpus because it is one of the highest-volume phrasings a search box receives, and leaving it out would make the tracked-keyword half of the corpus wrong. As a probe it is redundant: the carrier already supplies the geography, so near me adds nothing but tokens.

Comparison — someone has candidates and wants a reason to pick

The weakest rank-tracking rows and the strongest probes. A comparison query asks the engine to choose, which is exactly the behaviour an AI-visibility measurement is trying to observe.

IDTemplateRegisterRankProbe
C-01best {category} in {city}termweakyes
C-02top {category} near {area}termweakyes
C-03best {category} for {service} in {city}termweakyes
C-04best {category} in {city} {audience}termweakyes
C-05{category} in {city} with the best reviewstermnoyes
C-06most reliable {category} in {city}spokennoyes
C-07which {category} in {city} should I use for {service}?spokennoyes
C-08who's the best {category} near {area}, and why?spokennoyes — the why clause is what makes stance readable
C-09I've got three quotes for {service} in {city}. How do I choose who to go with?conversationalnoyes
C-10what's the difference between the main {category}s in {area}?conversationalnoyes
C-11I need {service} in {city} and I care more about {constraint} than price. Who fits?conversationalnoyes
C-12alternatives to {rival} in {city}termweakflagged — names a rival, not you
C-13{brand} vs {rival}termweakno — branded
C-14is {brand} or {rival} better for {service}?spokennono — branded

C-12 is the one row that names a business without begging its own answer, because the business it names is not yours. It is a legitimate displacement probe — when someone is already looking at a competitor, does the engine offer you? — and it must live in its own series, never pooled with the unbranded rows, because naming any business changes what the model retrieves.

Trust — someone has your name and is deciding whether to use you

Branded and subjective. You will "rank" #1 for all of these because it is your name, which is why rank is not the point: what matters is what the searcher finds when they arrive.

IDTemplateRegisterRankProbe
T-01{brand} reviewstermwatchno — branded
T-02{brand} {city} reviewstermwatchno — branded
T-03is {brand} any good?spokennoaudit
T-04{brand} complaintstermwatchno — branded
T-05is {brand} legit?spokennoaudit
T-06has anyone used {brand} for {service}?conversationalnoaudit
T-07what do people say about {brand}?conversationalnoaudit
T-08is {brand} licensed and insured?spokennoaudit

watch means: track it to see what appears alongside you, not to record a position. A page of #1s is the easiest report to produce and the least informative.

Logistics — someone has decided and needs one fact

Branded and objective. Answered straight out of your profile's structured fields, frequently with no click to anything. Useless as visibility probes and genuinely useful as a fact-accuracy audit: engines that are not grounded in Google's place data are reading pages about you, and pages about you go stale.

IDTemplateRegisterRankProbe
L-01{brand} opening hourstermnoaudit
L-02is {brand} open on {day}?spokennoaudit
L-03{brand} phone numbertermnoaudit
L-04where is {brand}?termnoaudit
L-05is there parking at {brand}?spokennoaudit
L-06does {brand} do {service}?spokennoaudit
L-07does {brand} take walk-ins?spokennoaudit
L-08how do I book with {brand}?spokennoaudit

An accuracy audit records something different from a visibility probe: not were you named but was the fact correct. Run the eight L rows once per engine, mark each answer right, wrong or absent, and you have a defensible finding — "one engine had our Sunday hours wrong on 2026-07-27" — that costs almost nothing and is immediately fixable.

SOCi's 2026 Local Visibility Index (~350,000 locations) reported profile accuracy of 68.3% on ChatGPT and 68.0% on Perplexity against roughly 100% on Gemini — around two-thirds on the web-grounded assistants against near-total accuracy on the Maps-grounded one. It is vendor-published and its methodology is only partly disclosed, so treat the figures as soft and the mechanism as sound (chapter 1 carries the full citation and its caveats).

Filling the slots: four worked packs

Templates are easy to misfill. The commonest error is writing the trade's vocabulary into {category}HVAC preventative maintenance rather than boiler service. These four packs are complete, fictional instantiations of the same seven rows, so you can see the shape before doing your own.

IDEmergency tradeCaféDental clinicAccountant
D-03plumber in Leedscoffee shop in Bristoldentist in Leedsaccountant in Bristol
D-05boiler repair Leedscold brew Bristolemergency dentist Leedsself assessment help Bristol
D-13I need a plumber today in Leeds — who can actually come out?I'm in Bristol city centre and want somewhere to work from for two hours with good coffee. Where?I've got toothache and need someone in Leeds today. Who takes emergencies?my tax return is due in Bristol and I've left it late. Who takes new clients?
C-01best plumber in Leedsbest coffee shop in Bristolbest dentist in Leedsbest accountant in Bristol
C-08who's the best plumber near Headingley, and why?who's the best coffee shop near Stokes Croft, and why?who's the best dentist near Chapel Allerton, and why?who's the best accountant near Clifton, and why?
T-03is Acme Plumbing any good?is Bramble Café any good?is Northgate Dental any good?is Halliday & Co any good?
L-02is Acme Plumbing open on Sunday?is Bramble Café open on Monday?is Northgate Dental open on Saturday?is Halliday & Co open on Friday?

The business names are invented. Substitute real ones only for the business you are actually measuring.

Two filling rules that save the set:

  1. {category} is the word a customer would say to a friend. If you would not say it out loud in a pub, it is not a category fill.
  2. {area} beats {city} for probes when the city is large. best dentist in London is a question with no answer; best dentist near Chapel Allerton is one a person would actually ask.

Building a set from the corpus

You cannot run 46 rows. Probe cost multiplies keywords × engines × runs, and a five-run window on three engines turns even six rows into 90 calls before a single rate exists (LSM-AI-36). Probing thirty rows once produces thirty anecdotes; probing six rows five times produces six measurements.

Select by cell, not by preference. A cell is an intent crossed with a place, and you fill each cell once:

CellRows to draw fromHow many
Discovery / primary areaD-01, D-03, D-04, D-052
Discovery / second areathe same rows, different {area} and coordinate1
Discovery / conversationalD-11 … D-151
Comparison / termC-01, C-02, C-031
Comparison / conversationalC-08, C-09, C-10, C-111
Trust — audit seriesT-03, T-05, T-071
Logistics — accuracy seriesL-01 … L-08run once, not on a schedule

That is a six-row unbranded probe set plus two audit series. The unbranded six are the ones that carry a visibility rate. Everything else is reported separately or not at all.

The second discovery cell deserves defending because people cut it first. It is the same template from a different coordinate, and it is the only duplicate worth paying for: the default probe point is the business's own coordinates, which is the single most flattering point available (LSM-AI-12).

Whether moving the coordinate moves an assistant's answer at all is an open question (LSM-AI-15) — which is a reason to record it, not a reason to skip it.

What must travel with every run

A corpus is only citable if the record says which row produced the answer. These fields are specific to the corpus; the full per-run record is in the AI visibility record schema.

FieldExampleWhy
Corpus versionLSM-corpus 1.0Rows change between versions
Row IDC-08The unit somebody else can reproduce
Slot fills{category}=dentist, {area}=Chapel AllertonTwo people can fill C-08 differently
Filled query, verbatimwho's the best dentist near Chapel Allerton, and why?The only field that makes the rest checkable
Carrier versioncarrier 1.0A carrier edit resets the series
Coordinate53.8321, -1.5460An input to the run, never a property of the result
Engine and call shapegemini / grounded generateContentFindings never transfer between engines
Series tagunbranded / displacement / auditKeeps flagged rows out of the headline rate

The series tag is the field people skip and then regret. Without it, a C-12 displacement run and a C-01 visibility run land in the same average, and the average is now about nothing.

Running it

Procedure · by hand · Where: any assistant, in a browser · Cost: free · Time: ~30 min

You need: your slot fills written down, and the coordinate you are asking from.

  1. Fill six unbranded rows using the cell table above. Write the filled text out in full — do not run a template with a slot still in it.
  2. Wrap each in the carrier prompt with your coordinates.
  3. Open a fresh chat per run. Conversation history contaminates the next answer.
  4. Paste the answer verbatim into a spreadsheet, one row per run, with every field from the table above.
  5. Repeat the whole pass five times. Same rows, same fills, same coordinate.
  6. Only now count anything.

What good looks like. Thirty rows of stored answer text, six cells with five runs each, and no cell you ran more often than the others because it was going well.

If it went wrong. If an engine answers with generic advice and names nobody, that run leaves the recommendation denominator rather than counting as a miss (LSM-AI-19). If it names businesses but not yours, that is a miss and belongs in the numerator's complement. They are different outcomes and must be stored as different values.

Procedure · in the app · Where: Rankings (/b/{businessId}/rankings), then AI Visibility (/b/{businessId}/ai-visibility) · Cost: paid · Time: ~25 min

You need: your six filled unbranded rows.

  1. In Rankings, add each filled row with Track. The AI check runs on active tracked keywords, so a row that is not tracked cannot be probed.
  2. For the second-area cell, add the same phrase again with a different Search from value. It saves as its own row rather than being rejected as a duplicate, because a tracked keyword's identity is the phrase plus its location plus its language, not the phrase alone. (Code-verified 2026-07-27.)
  3. In AI Visibility, scroll to Where AI mentions you, select exactly your six rows, and read the count on the bar: N keywords × M engines = K checks.
  4. Press Check N selected and let the batch counter finish. Repeat four more times with the identical selection, recording the date of each pass.
  5. Read the rates from the tiles and the Sources cited by AI table. Both are free — they read what the paid checks stored.

What good looks like. Five passes with an identical selection, and a note of the dates. Uniform run counts are what make the consistency figure interpretable.

If it went wrong. A cell reading Sample means that engine is not connected: fixture rows, excluded from every rate, an unmeasured column rather than a zero. Do not let a branded row into the selection — the whole batch is then measuring the model's agreeableness.

Without any tool this is the spreadsheet from the first procedure, and it is completely valid. It simply does not keep history, which is most of the argument for tooling (doing it without SEOG).

Rows that must never be pooled

Four exclusions, each with a mechanism rather than a preference behind it.

Branded rows never enter a visibility rate. A prompt containing your name puts the name in the context window, so the answer is a comment on your own input. You have measured recall, not visibility (LSM-AI-13). This includes the subtle case: filling {category} with the way your marketing describes you — artisanal third-wave espresso bar — is a branded prompt in disguise.

Displacement rows (C-12) keep their own series. Naming any business changes retrieval, even when it is not yours.

Temporal rows (D-07, D-08) keep their own series or stay out. "Open now" and "24 hour" depend on what the engine believes the time is, which you do not control and cannot record accurately (inference — the mechanism is that the model has no reliable clock for the searcher's timezone; we have not measured how often it errs).

Rows with a short or generic {brand}. If the business name is a common word or a substring of a nearby brand — Coffee in a market containing Coffee Republic — automatic self-matching over the answer text will over-count in the most flattering possible direction (LSM-AI-22). Verify those matches by eye against the stored answer, and say in the report that the rate is an upper bound.

Scope note. This corpus is for measuring the visibility of a business you own or are engaged to work on. Running it at scale across every business in a market to assemble a redistributable dataset of names, addresses and reviews is a different activity with its own terms-of-service exposure — see storing Google data legally. The line is not the prompt; it is what you keep and who you give it to.

Known biases of corpus v1.0

Stated so that anyone citing it can weigh them, and so v1.1 has somewhere to start.

  • English, and British-leaning. The phrasings, and the assumption that {city} and {area} are the natural geographic units, come from English-language local search. Other languages carry local intent differently and this corpus has not been tested in any of them.
  • Weighted to services over retail. Discovery and comparison rows assume a business someone hires or visits deliberately. Impulse and footfall retail are under-represented.
  • No price rows. how much does {service} cost in {city} is a real and growing query shape, and it is absent because we have not decided whether an engine naming price ranges without naming businesses is a punt or a distinct outcome.
  • No multi-location rows. A chain asking "which of our branches gets named" needs a different design; see multi-location and franchise.
  • Service-area businesses are poorly served. {area} assumes a place the business is in. A trade covering forty postcodes from a hidden address needs rows anchored to the customer's location rather than the business's, and this version does not distinguish them (service-area businesses).
  • The register ladder is untested. Three registers are offered because assistants are asked longer questions than search boxes are. Whether register changes which businesses are named, holding everything else constant, is unmeasured.

Two open questions this corpus was built to make answerable, and which nobody has published answers to:

Does register change the answer? Take one market, one engine, one coordinate. Run D-03 and D-13 — the same underlying need at two registers — twenty times each. Compare mention rates against the run-to-run noise floor, not against each other (LSM-AI-16). Twenty runs still leaves a worst-case standard error of roughly 11 points — 0.5 / √n, the arithmetic and its table are in measuring AI visibility — so only a large difference is readable.

Do comparison rows really out-perform discovery rows as probes? The claim in this manual is a mechanism argument: comparison queries ask the engine to choose, so they produce more named businesses and more readable stance. Nobody has published the rate at which each intent produces a recommending answer at all. Run the six discovery and six comparison rows twenty times each on one engine, count how many answers named any business, and you have it.

If you run either, publish the query set, the run count and the date. Two of the three are missing from most work in this area.

Versioning, forking and citing

Version 1.0 · 2026-07-27 · 46 rows (D-01…D-16, C-01…C-14, T-01…T-08, L-01…L-08). Initial publication.

IDs are permanent. A row that turns out to be badly worded is corrected in place with a note, or retired and left in the table marked retired — never renumbered, never reused for something else, because someone's stored records point at it.

To cite:

The Local SEO Manual, "The local prompt corpus", version 1.0 (2026-07-27).
https://learn.seog.ai/appendix/the-local-prompt-corpus

To fork: the repository is public at github.com/seog-ai/local-seo-manual. Copy the tables, change what your market needs, and say in your version what you changed and which version you started from. A fork that keeps the IDs stable stays comparable with everyone else's data; a fork that silently renumbers does not.

Corrections are the most valuable contribution here — particularly a row that reliably produces refusals, a vertical the packs handle badly, or a language the templates do not survive. Contributing has the process; include what you ran, when, and against which engine.


Pairs with: What people actually search · How an AI assistant answers a local question · Building a tracked set that tells the truth · Measuring AI visibility · AI engine probe recipes · The AI visibility record schema