# Negative keyword list builder by theme — research and method

Last reviewed 2026-09-29.

## What this tool is allowed to assert

The project rule is that platform behaviour is a fact and account judgement is
not. This tool asserts only the first kind.

**Asserted, as documented platform behaviour:**

- A negative keyword added at account level applies to every campaign, every
  location and every device. Campaign-level negatives leave other campaigns free
  to bid on the term.
- An **exact** negative blocks the query that matches it and its close variants.
  It is specific and reversible.
- A **phrase** negative blocks any query containing the phrase, including
  longer queries that have never been seen and may have been wanted.
- Negative keywords match on whole words. `job` does not match `jobs`, and
  `care` does not match `career`.

**Explicitly not asserted, because it is the operator's call:**

- Which terms are waste. A term with zero recorded conversions may still produce
  phone calls, in-store visits, or conversions tracked where Google cannot see
  them. It may also be past the attribution window. The tool shows the number
  and stops.
- Whether a theme is safe to block. That depends on the conversion lag for the
  operator's product, which this page cannot know.
- Whether a phrase negative is appropriate. Also a judgement, and a
  consequence-bearing one.

## Why whole-word matching, and not substring

Substring matching is the obvious implementation and it is wrong here. A rule
for `care` under substring matching catches `career`, `caress` and `scared`. A
rule for `art` catches `party`, `charter` and `heart`. Every one of those is a
search a real person typed, and blocking them is the exact damage the operator
is trying to prevent.

A negative keyword does not behave that way, so the tool must not either.
Tokens are produced by splitting on anything that is not a letter or a digit,
which means `free-running` yields `free` and `running`, matching how the platform
evaluates the phrase.

Accents are **preserved**, not stripped. Folding `café` to `cafe` would let a
rule the operator did not write block a word they did not intend to block. `café`
and `cafe` are treated as different, which is also how a negative keyword
behaves. A test covers this specifically, because it is a deliberate
non-obvious choice.

## Why these three failure modes get first-class treatment

The most expensive outcome for this category is not a wrong number. It is a
plausible-looking list that is pasted without being read, which then suppresses
converting traffic for months. Three situations produce that outcome, and each
is reported rather than smoothed over:

**1. A term matching two themes.** The rules disagree. Picking silently means
the operator blocks a term under a rationale they did not intend. The tool
assigns it to the first rule that matched — deterministic, and the one the
operator wrote earliest — and names both themes in the decisions list.

**2. A keyword matching nothing.** A silent no-op is worse than an obvious
failure, because the operator believes a rule is running when it is not. Dead
keywords are listed per theme, and a theme matching nothing at all is still
rendered and labelled.

The dead-keyword message carries one extra piece of reasoning. Because
whole-word matching is the cause of most dead keywords, when another keyword in
the same theme *did* match, the tool says so and names both: `job` fails to
match `jobs` because matching is whole-word, and listing both is the fix. Without
that hint the reasonable response to "this keyword matched nothing" is to delete
it, and then the same gap reappears next quarter.

**3. Duplicate rows.** A search-terms report containing the same term twice
normally means the same term in two campaigns. Summing is correct. Summing
*silently* is not, because the whole page ranks themes by the resulting spend,
so an undisclosed merge misstates the number the ranking depends on. The merge
is performed and reported.

## What is deliberately absent

- **No recommendation or ranking of themes.** The tool produces no advice on
  what to block, and no "high confidence" framing. The conversion lag argument
  is too important to bury under a score.
- **No stemming.** A negative keyword does not stem, and a tool that stems would
  block more than the platform would. This is a case where matching the
  platform's literal behaviour is more useful than being clever.
- **No scope advice.** The right scope for a negative list depends on which
  campaigns exist in the account, which this page cannot see.
- **No benchmark waste tolerance.** Tool 02 takes a tolerance the operator sets.
  This tool takes no position on how much waste is acceptable, because that is
  the operator's margin decision.
- **No report fetching and no upload.** Search terms reveal who is searching for
  a business and what they typed. Nothing is transmitted in either mode.

## Known limits

- Whole-word matching means morphological variants must be listed
  individually. Documented on the page, and now explained inline when a keyword
  fails to match.
- A term with no spend figure is grouped and counted but cannot be ranked by
  money. The count of such terms is reported.
- The tool cannot see conversion lag, offline conversions, or which campaigns a
  list was built from, so it cannot advise on scope or safety.
- Theme names are free text. Two rules with the same name are treated as
  separate themes rather than merged, because guessing that two differently
  worded names mean the same theme is the same class of error as guessing a
  conflict.

## Verification

`tests/run.mjs` covers the whole-word rule, accent preservation, the accent-free
near-miss hint, conflicts, malformed rules, silent themes, duplicate merging,
multi-word keywords, the unthemed bucket, the empty state, absence of
`NaN`/`Infinity`, and a real CSV export parsed with an RFC 4180 parser to
confirm no row is wider than its header and no quote is left unbalanced.
