Intent Matching You Can Measure: Fingerprint the SERP First
Record eleven fields for each ranking URL, sample the set for 14 to 30 days, then score your draft's distance from whatever held still. Intent as a number.
The instruction “match the search intent” survives because it is unfalsifiable. Two people can look at the same ten results, reach opposite conclusions about what the page should be, and neither can be shown wrong, because nothing was recorded.
The fix is mechanical: write the observation down in a fixed schema, take it more than once, and turn the difference between it and your draft into a number. This post is that procedure, and it is the parent post for the rankings work on this site, so the record it defines is meant to be reused verbatim rather than reinvented per project.
Why the four-intent taxonomy stopped being an instruction
Informational, navigational, commercial, transactional. The taxonomy is not wrong. It is just answering a different question from the one you have at the moment you open an empty file.
Google’s own published treatment of intent is more useful, and it sits in the Search Quality Rater Guidelines (general guidelines, dated 11 September 2025). Section 12.7 describes queries as having “one or more of the following intents” and names four: Know, some of which are Know Simple; Do; Website; and Visit-in-person. Section 12.7.5, “Queries with Multiple User Intents,” opens with “Many queries have more than one likely user intent.” Section 12.5 grades interpretations as dominant, common, or minor, and section 12.6 is called “Query Meanings Can Change Over Time.”
Those four sections together say three things the four-box version hides. Intent is plural per query. Interpretations are ranked by share, not by type. And the ranking is time-dependent, so a classification you made in January is an observation about January.
None of that tells you what page to build. It does tell you why a taxonomy cannot: the label sits on the query, and your decision is about the artifact. The bridge between them is the result set, the only place where the ranking system has already committed to an answer.
The fingerprint record
The unit is one result, in one sample, for one (query, locale, device) triple. Never mix devices or locales into a single record set, and never compare a sample collected through a logged-in browser against one collected through an API. Whatever you use to collect, the requirement is that the method is byte-for-byte identical across every sample, because you are about to interpret differences between samples as signal.
The eleven fields
{
"url": "https://example.com/guide-to-x",
"rank": 3,
"page_type": "guide",
"structure": ["toc", "step_list", "comparison_table", "code_block"],
"freshness": {"visible_date": "2026-06-18", "date_type": "updated"},
"media": {"images": 9, "videos": 1, "interactive": 0},
"author": {"named": true, "entity_page": "https://example.com/authors/x"},
"commercial": {"sells": false, "pricing_visible": false, "cta": "newsletter"},
"structured_data": ["Article", "BreadcrumbList", "Organization"],
"result_features": ["sitelinks_group", "byline_date", "text_result_image"],
"word_count": 2410
}
Three of those fields need their vocabularies pinned down or the record stops being comparable across projects.
page_type is a closed enumeration you commit to once: guide, listicle, comparison,
tool, docs, forum_thread, category, product, news, profile. Closed, because an
open field turns into free text and free text cannot be counted.
structured_data records the schema.org types the page emits, checked against what is currently
eligible rather than what you remember being eligible. The
structured data gallery
is the live list, and types leave it: HowTo rich results were withdrawn and FAQ rich results
narrowed to well-known authoritative government and health sites in the
August 2023 change, and
the sitelinks search box was
removed globally from
November 2024. A fingerprint that treats FAQ markup as a format signal in 2026 records a fossil.
result_features uses the names in the
visual elements gallery:
title link, snippet, attribution, byline date, sitelinks group, text result image, rich
attributes, plus the related questions group and related searches group at page level. Google’s
vocabulary costs nothing here and keeps your notes legible when the surface changes.
Wrap the ten result records in a per-sample envelope that carries the page-level state:
{
"query": "search intent analysis",
"locale": "en-US",
"device": "mobile",
"sampled_at": "2026-06-24T09:00:00Z",
"collector": "collector-v2",
"serp_elements": ["related_questions_group", "video_result", "related_searches_group"],
"ai_overview": {"present": true, "source_urls": []},
"results": []
}
Why word count is the weakest field
Word count is in the record because leaving it out invites someone to add it back badly. It is the field I trust least, for five reasons that compound.
It is downstream. Length is a consequence of structure and entity coverage, not an input to them. A draft that covers the consensus entities with the consensus structures arrives at a length; a draft padded to a target arrives at the number without any of the causes.
It is not comparable across pages. Your extractor makes judgement calls about navigation, comments, footers, product listings, transcripts and code, and different templates trip different calls. A large share of the variance you measure across a ranking set is variance in your parser.
Its distribution is wide and heavy-tailed, so “the average of the top 10 is N words” compresses a distribution that usually has no centre worth naming.
It is collinear with fields you already recorded. Page type and structural element count predict most of it, so it adds little independent information to the score.
And no Google documentation ties page length to eligibility for anything. The only documented
length control in this area runs the other way: max-snippet limits the snippet Google may
show, and the
featured snippets documentation
notes that “the shorter your max-snippet rule setting, the less likely the page will appear as
a featured snippet.” That is a statement about the snippet, not about the page.
Record it. Use it as a sanity guard, not as a target. It gets weight zero in the scoring below, and that is deliberate.
Volatility first: sampling before conclusions
One snapshot cannot distinguish a pattern the ranking system enforces from an accident of the day you looked. This is the step that kills most published “the SERP wants listicles” conclusions, and it is the step that gets skipped, because it costs two to four weeks of waiting before you are allowed to have an opinion.
Sample the same triple daily for 14 days minimum, 30 where the query has news or seasonal pressure. Google documents “various ‘query deserves freshness’ systems designed to show fresher content for queries where it would be expected” in the ranking systems guide; where such a system is engaged, a single snapshot is close to worthless and the volatility itself is the thing worth knowing.
The stable set
Two numbers come out of the sample series. Presence rate per URL: in how many samples did this URL appear at all. Carryover per adjacent pair of days: what share of yesterday’s ten survived into today’s ten.
from collections import Counter
from statistics import median
def carryover(samples):
days = sorted(samples)
return median(len(set(samples[a]) & set(samples[b])) / len(samples[a])
for a, b in zip(days, days[1:]))
def stable_set(samples, min_presence=0.8):
n = len(samples)
seen = Counter(url for day in samples.values() for url in set(day))
return {url for url, k in seen.items() if k / n >= min_presence}
samples is a dict of date to the ordered list of ten URLs. The stable set is the fingerprint
population; everything else is churn and gets excluded from every calculation that follows.
Starting thresholds, to be calibrated on your own vertical rather than adopted as law:
| Median carryover | Reading | What to do with the fingerprint |
|---|---|---|
| ≥ 0.9 | At most one result of ten turns over daily | Treat the consensus as enforced; conform on the high-stability fields |
| 0.7 – 0.9 | Partially enforced, contested tail | Conform only on fields whose stability clears 0.7 |
| < 0.7 | The set is not settled | Conformity buys little; spend the effort on the differentiator instead |
If carryover is low and the URLs entering the set carry recent visible dates, that is consistent with a freshness system being engaged. Consistent with, not proof of: I can observe the churn and the dates, and the attribution to a named system is my reading of it. The same care applies to any mechanism you infer from a result set, which is the subject of grading evidence about Google systems.
Per-field stability
Set membership is not the only thing that moves. A set can be perfectly stable while the modal
page type flips between guide and listicle week to week, and you want to know that before
you decide which one to build.
For each field, take the modal value across the stable set within each sample, then measure how often that mode is the same value across samples, and how strong the agreement was inside each sample.
from collections import Counter
def field_stability(samples, field, stable):
modes = []
for rows in samples.values():
vals = [str(r[field]) for r in rows if r["url"] in stable]
if vals:
value, hits = Counter(vals).most_common(1)[0]
modes.append((value, hits / len(vals)))
if not modes:
return 0.0
top, persisted = Counter(value for value, _ in modes).most_common(1)[0]
agreement = [share for value, share in modes if value == top]
return round(persisted / len(modes) * sum(agreement) / len(agreement), 3)
Sort the array-valued fields before they reach that function, or you will be measuring element ordering rather than element membership. Stability then lands in 0 to 1: persistence times agreement. A field where nine of ten stable pages carry the same value in every sample scores near 1. A field split five to five that also flips its majority between samples scores near 0.25, and a field that scores near 0.25 has no business constraining your draft.
This is the number that makes the whole method self-limiting. You are not conforming to the SERP. You are conforming to the parts of it that held still.
Entity coverage without a vendor score
The gap analysis every tool sells is a term-frequency comparison with a proprietary weight on top. You can compute the useful part yourself, and the useful part is document frequency, not term frequency.
Extract main content from each stable-set page with a readability-style extractor, pull noun phrases and named entities, normalise case and plurals, then count in how many of the stable-set pages each entity appears at least once. How many times a page repeats a term is a property of that page’s author. How many independent pages mention it at all is a property of the topic.
import math
from collections import Counter
def wilson(k, n, z=1.96):
p, d = k / n, 1 + z * z / n
centre = (p + z * z / (2 * n)) / d
half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
return round(centre - half, 3), round(centre + half, 3)
def entity_df(pages):
df = Counter()
for terms in pages.values():
df.update(set(terms))
n = len(pages)
return {t: (k, wilson(k, n)) for t, k in df.items()}
Deciding which part of the gap is noise
The Wilson interval is there because ten pages is a very small sample and the gap list will tempt you to act as if it were not. At n = 10, an entity present on 3 pages has a 95% interval of roughly 0.11 to 0.60, and one present on 6 pages has roughly 0.31 to 0.83. Those intervals overlap across most of their range. The ordering between a 3-of-10 entity and a 6-of-10 entity is not something your sample can resolve, and any tool that hands you a ranked gap list with two decimal places is reporting precision it does not have.
Three consequences follow.
Only act on the top and the bottom. Entities at 8 of 10 or above are consensus and their absence from your draft is a real gap. Entities at 2 of 10 or below are one or two authors’ idiosyncrasy. The middle band is unresolvable at this sample size and belongs to judgement about the topic, not to the count.
Pooling across days does not help. Thirty daily samples of the same stable set is still ten documents; the extra rows are repeated measurements of the same pages. If you need a larger n, widen the set to the top 20 or pool sibling queries that share a stable set. More days buys stability, not statistical power, and those are different purchases.
Strip the predictable contaminants before counting: brand names of the ranking domains, boilerplate that survived extraction, author names, and anything appearing only on pages that failed the presence-rate filter. Contaminants inflate document frequency in exactly the band where you can least tell signal from noise.
Format distance as a score
Now the fingerprint becomes a number. For each scored dimension compute a distance in 0 to 1 between your draft and the stable-set consensus, weight it, and weight it again by that field’s stability.
Distances are deliberately crude. page_type is binary: 0 if your draft is the modal type, 1 if
it is not. structure is one minus the share of consensus structural elements your draft has.
entities is one minus the share of the 8-of-10-and-above consensus entities your draft covers.
freshness is 0 if you will publish and display a date consistent with the stable set’s date
type, 1 if you will show no date where every stable page shows one. media and commercial are
binary against the modal value.
WEIGHTS = {"page_type": 0.30, "structure": 0.25, "entities": 0.20,
"freshness": 0.10, "media": 0.10, "commercial": 0.05}
def format_distance(gaps, stability):
num = sum(WEIGHTS[f] * stability.get(f, 0.0) * d for f, d in gaps.items())
den = sum(WEIGHTS[f] * stability.get(f, 0.0) for f in gaps)
return round(num / den, 3) if den else 0.0
Weights you tune
Those six numbers are a starting point, not a law, and I am explicit about that because weights published in a post get quoted back as findings. They encode one judgement: that being the wrong kind of page is a harder problem than being the wrong length, and that structure and entity coverage are the two dimensions a writer can act on.
Calibrate them against your own outcomes. Keep the distance score for every brief you ship with the query it targeted, then revisit once those pages have a couple of months of impressions. If your low-distance and high-distance pages perform indistinguishably on a dimension, that dimension’s weight is too high. It is a tracking table, not a research programme, and it is the only thing that turns these six numbers from my priors into your calibration.
Note what the normalisation does. Dividing by the summed effective weight means fields with near
zero stability drop out of both numerator and denominator instead of dragging the score toward
zero. A query whose result set agrees on nothing produces a score built from the two or three
fields that did agree, which is the honest answer. And word_count has no entry in WEIGHTS at
all, for the reasons above.
When the SERP is a trap
Three configurations where a well-computed fingerprint is a well-computed answer to the wrong question. Check for all three before you write the brief.
Brand-locked sets. If most of the stable set is first-party pages of a handful of named organisations, the query carries a large Website intent in the rater guidelines’ sense and the ranking system is not choosing between formats. It is resolving entities. The measurable tell is the composition of the stable set: count distinct organisations rather than distinct domains, and treat any set where a few brands hold most of the durable positions as one where format conformity earns nothing.
Stale sets: high carryover, old visible dates across the stable set, no new entrants over the whole window. The pattern is real and enforced, and it is enforced by inertia rather than preference; nothing has challenged the incumbents recently. My reading is that this is the most attractive configuration on the list and the one where copying the fingerprint is least useful, because it describes pages built for a different year.
Feature-dominated pages. The stable set is fine, but the page also carries image results, video results, a related questions group and an AI Overview above or among them. The text results are ranked the same way as ever; they are competing for a smaller pool of clicks. The fingerprint tells you what to build and says nothing about whether the position is worth having, and those two decisions get collapsed constantly.
There is a fourth trap that is not about the SERP at all: two of your own URLs already match the fingerprint you just computed. That is a cannibalization question with its own decision rule, and writing a third page into that set makes it worse.
AI Overviews change the payoff, not the fingerprint
Record ai_overview as a per-sample field: presence, and the set of source URLs, so you can
compute overlap between the AI Overview’s sources and your stable set. Do not let it touch the
format fields, because the documentation gives no reason for it to.
Google’s AI features documentation states that to appear in AI features “a page must be indexed and eligible to be shown in Google Search with a snippet, fulfilling the Search technical requirements. There are no additional technical requirements,” and that “there’s also no special schema.org structured data that you need to add.” The optimization guide for generative AI features goes further and tells you not to add AI text files or markup such as llms.txt, not to break content into tiny pieces, and not to “write in a specific way just for generative AI search.” Nothing there is a new fingerprint field.
What changes is the click economics, and the reporting is where most writing on this goes wrong. The currently documented position:
Impressions and clicks from AI features are included in the overall Performance report, within the Web search type, not broken out. Search Console’s impressions, position and clicks reference says standard impression rules apply to AI Overviews, that “clicking a link to an external page in the AI Overview counts as a click,” and that “an AI Overview occupies a single position in search results, and all links in the AI Overview are assigned that same position.” There is no AI Overviews entry in the search appearance dimension, so you cannot filter these clicks out of your existing data.
The separate Generative AI performance report, announced in June 2026, is narrower than most summaries of it suggest. It reports impressions only. No clicks, no position, no query dimension. Chart data aggregates by property, so two of your results inside one generative feature count as a single impression; table data aggregates by page. It is still rolling out, so not every property has it, and the usual row limits and thresholds apply.
The operational conclusion is narrow. You can find out whether you were shown inside a generative feature. You cannot, from Search Console alone, price what that appearance cost or earned you: the clicks are pooled into the same web figures as everything else, and the impressions are reported without them. That is why AI Overview presence belongs in the fingerprint as a field that adjusts expected value rather than format, and why the honest follow-up is the CTR-at-constant-position decomposition in content decay triage rather than a rewrite. Measuring the generative surface directly means handling the variance problem first, which is its own sixty-day instrument.
From fingerprint to an executable brief
The output of all of this is not a document about a SERP. It is a specification with a number on it, and every line of it traces back to a recorded field.
- Page type, plus its stability score. If stability is under 0.5, say so in the brief and state which type you chose and why, because the data did not choose it.
- Required structural elements, listed in descending order of stability, with a cut line. Elements below the cut are options, not requirements.
- Consensus entities at 8 of 10 or above, with their document frequency. Entities in the middle band go in a separate list marked as judgement calls, never merged with the first.
- Freshness commitment: what date will display, what date type, and what triggers a revision.
- Author and commercial requirements: whether a named author with an entity page is the consensus, and whether pricing appears above the fold on the stable set.
- Declared non-conformity: the fields where you are deliberately not matching, each with a reason. This is the section that earns its keep, because it is the one that survives the argument three months later about whether the page failed for the reason someone remembers.
- The distance score the draft is expected to hit, and the re-fingerprint date.
- Word count, as a range with a floor and no ceiling, labelled as a guard rather than a target.
Re-fingerprint at 60 to 90 days, and again whenever the page moves more than a couple of positions without you touching it. The set you measured has its own half-life, which section 12.6 of the rater guidelines says out loud about query meanings and which your carryover number measures directly. A brief built on a fingerprint from last spring is a brief built on a memory.
None of this makes the writing good. It removes one specific class of failure: the page that was competently written as the wrong artifact, which is the failure that costs a month and is invisible until the impressions arrive. The rest of the work is still work, and no amount of internal link equity rescues a page that is the wrong shape for the set it is trying to enter.