Project Spec: RegionalPestGuide Programmatic Content Expansion
States covered:
Version: 0.1 Status: Draft Author(s): Evan Maxon, Kai Approved by: Date: 2026-03-17 Repository: emaxon/regionalpestguide GitHub Project: Content Empire Tracks: Programmatic SEO expansion for regionalpestguide.com
RegionalPestGuide currently has 11 pest pages, 8 region pages, and 29 blog posts. There are zero intersection pages (pest × region), which means the site misses thousands of high-intent long-tail search queries like “termite control southeast”, “mosquito prevention pacific northwest”, and “ant control great plains”. These intersection queries have strong commercial intent and low competition. The site also lacks 5 common household pest categories and needs more blog content to build topical authority.
The GardeningByZone sister site already uses a Python generator script (generate-plant-zone-pages.py) to programmatically create intersection pages from data files. This spec replicates that proven pattern for RegionalPestGuide: a Python script reads existing pest and region collection files, then generates unique intersection markdown pages with rich front matter and body content.
generate-pest-region-pages.py generates 128 intersection pages (16 pests × 8 regions) in collections/_pest_regions/_layouts/pest-region.html renders all intersection pages correctly in Jekyll with zero Liquid errors_config.yml includes pest_regions collection with output: true_layouts/pest.html cross-links to all region sub-pages for that pest_layouts/region.html cross-links to all pest sub-pages for that regioncollections/_pests/ with substantive content_posts/ (21 new), each 800+ words with affiliate linksbundle exec jekyll build completes with zero errorsepmlabs-20In scope:
pest-region layoutOut of scope:
jekyll-sitemap plugin automatically)| Component | Choice | Notes |
|---|---|---|
| Site generator | Jekyll (existing) | Ruby gem, uses Liquid templates |
| Page generator | Python 3.8+ | Standalone script, no pip dependencies beyond stdlib |
| Templating | Liquid | Jekyll’s native template engine |
| CSS framework | Bulma (existing) | Classes like section, container, columns, box, button is-success, notification is-light is-info |
| Affiliate | Amazon Associates | Tag: epmlabs-20 |
regionalpestguide/
├── generate-pest-region-pages.py # NEW — T01
├── _config.yml # MODIFY — T03
├── _layouts/
│ ├── pest.html # MODIFY — T03
│ ├── region.html # MODIFY — T03
│ ├── pest-region.html # NEW — T02
│ ├── post.html # existing, unchanged
│ └── default.html # existing, unchanged
├── collections/
│ ├── _pests/
│ │ ├── ants.md # MODIFY — T03 (add region_pages: true)
│ │ ├── termites.md # MODIFY — T03
│ │ ├── ... (11 existing)
│ │ ├── centipedes-millipedes.md # NEW — T04
│ │ ├── earwigs-silverfish.md # NEW — T04
│ │ ├── stink-bugs.md # NEW — T04
│ │ ├── boxelder-bugs.md # NEW — T04
│ │ └── carpet-beetles.md # NEW — T04
│ ├── _regions/
│ │ ├── southeast.md # MODIFY — T03 (add pest_pages: true)
│ │ └── ... (8 existing)
│ └── _pest_regions/ # NEW directory — T01
│ ├── ants-southeast.md # Generated by script
│ ├── ants-northeast.md
│ └── ... (128 total after T04)
└── _posts/
├── ... (29 existing)
└── ... (21 new — T05)
generate-pest-region-pages.py follows the proven generate-plant-zone-pages.py pattern from GardeningByZone:
collections/_pests/*.md and collections/_regions/*.md, parse YAML front matter from each file to extract pest_id, title, emoji, region_id, states, key_pestsPEST_REGION_DATA dictionary embedded in the script containing unique facts, seasonal activity, regional species, and prevention tipscollections/_pest_regions/{pest_id}-{region_id}.md with complete YAML front matter and unique body contentcollections/_pest_regions/ to ensure idempotencyThe script must be runnable with: python3 generate-pest-region-pages.py (no arguments, no pip install).
---
title: "Termites"
description: "Identify, prevent, and treat termite infestations..."
pest_id: "termites"
emoji: "🐜"
region_pages: true # NEW — added by T03
---
---
title: "Southeast"
description: "Pest control for the Southeast US..."
region_id: "southeast"
key_pests: ['termites', 'mosquitoes', 'cockroaches', 'fire ants', 'fleas']
states: "Alabama, Florida, Georgia, Louisiana, Mississippi, North Carolina, South Carolina, Tennessee, Virginia"
pest_pages: true # NEW — added by T03
---
---
layout: pest-region
title: "Termite Control in the Southeast"
description: "How to identify, prevent, and treat termites in Alabama, Florida, Georgia, and other Southeast states. Region-specific termite control advice."
pest_id: "termites"
region_id: "southeast"
pest_name: "Termites"
region_name: "Southeast"
permalink: /pests/termites/southeast/
states: "Alabama, Florida, Georgia, Louisiana, Mississippi, North Carolina, South Carolina, Tennessee, Virginia"
key_pests: ['termites', 'mosquitoes', 'cockroaches', 'fire ants', 'fleas']
amazon_search: "termite control southeast"
---
---
layout: post
title: "How to Get Rid of Ants in Texas Heat"
description: "Texas-specific strategies for ant control in extreme summer heat..."
categories: [ants, pest-control]
tags: [ants, southwest, texas, summer, treatment]
date: 2026-03-18
---
| Decision | Rationale |
|---|---|
| Python script (not Jekyll plugin) | Matches GBZ pattern; no Ruby gem dependencies; simpler for workers |
| Embedded data dict in script | Self-contained; no external data files to manage; unique content per combo |
collections/_pest_regions/ directory |
Jekyll collection convention; underscore prefix required |
Permalink /pests/{pest}/{region}/ |
Nests under existing /pests/{pest}/ URL structure for SEO |
| Delete-and-regenerate idempotency | Simpler than diffing; ensures clean state on every run |
0.md stub excluded from generation |
File collections/_pests/0.md is a stub/placeholder; script skips files where pest_id is missing or empty |
| Separate layout file | Intersection pages need breadcrumb, seasonal table, and cross-links that differ from pest or region layouts |
bundle exec jekyll build must complete with zero errors and zero Liquid warnings_site/pests/ contains expected intersection page directoriespython3 -c "import ast; ast.parse(open('generate-pest-region-pages.py').read())" — syntax checkdiff output should show zero changes on second runwc -w)description values across intersection pagestag=epmlabs-20?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internaltitle and description front matter/pests/{pest-slug}/{region-slug}/ pattern (trailing slash)title front matter on every page{pest} control {region} or {pest} in {region}Per-task self-check commands specified in each task section.
cd /path/to/regionalpestguide && bundle exec jekyll build 2>&1
Must produce zero errors. Liquid warnings about missing variables are errors — fix them.
# Verify intersection page count
ls collections/_pest_regions/*.md | wc -l # expect 128 after T04
# Verify blog post count
ls _posts/*.md | wc -l # expect 50 after T05
# Verify no placeholder content
grep -rl "TODO\|Lorem\|FIXME\|placeholder" collections/_pest_regions/ _posts/ && echo "FAIL: placeholder content found" || echo "PASS"
generate-plant-zone-pages.py pattern (reads collection files, generates intersection markdown with rich front matter and unique body content)Description:
Create generate-pest-region-pages.py in the repository root. This Python 3.8+ script (stdlib only — no pip dependencies) programmatically generates intersection pages for every pest × region combination.
Script behavior:
collections/_pests/*.md — for each file, extract YAML front matter fields: pest_id, title, emoji. Skip any file where pest_id is missing or empty (this excludes the 0.md stub).collections/_regions/*.md — for each file, extract YAML front matter fields: region_id, title, states, key_pests.--- delimiters, use yaml.safe_load from stdlib yaml module — note: yaml is NOT stdlib. Instead, parse front matter manually by splitting on --- and extracting key-value pairs with string splitting on : . Handle YAML list syntax ['item1', 'item2'] by stripping brackets and splitting on commas).PEST_REGION_DATA dictionary with unique content for every pest × region combination. Structure:
PEST_REGION_DATA = {
("termites", "southeast"): {
"intro": "The Southeast's warm, humid climate makes it the #1 region in the US for termite damage. Subterranean and Formosan termites cause over $2 billion in damage annually across states like Florida, Georgia, and Alabama. The long warm season means termites remain active nearly year-round, with swarming events as early as February.",
"species": [
"Eastern Subterranean Termite — the most common species across all Southeast states",
"Formosan Termite — established in Louisiana, Mississippi, Florida, and spreading northward",
"Drywood Termite — coastal areas of Florida, Georgia, and the Carolinas"
],
"seasonal_activity": {
"Spring": "Peak swarming season (Feb–May); inspect foundations for mud tubes after rains",
"Summer": "Maximum colony activity; highest wood consumption rates; schedule professional inspections",
"Fall": "Continued activity in warm states; Formosan colonies still aggressive in FL/LA",
"Winter": "Reduced but not dormant; subterranean termites active below frost line year-round in Southeast"
},
"prevention_tips": [
"Maintain at least 6 inches between soil and wood siding — critical in humid Southeast climates",
"Install termite bait monitoring stations around foundation perimeter every 10 feet",
"Fix all moisture issues immediately — Southeast humidity accelerates wood decay that attracts termites",
"Schedule annual professional termite inspections — required by many Southeast home insurance policies",
"Remove dead trees and stumps within 20 feet of your home — common Formosan termite nesting sites"
],
"facts": [
"Florida has the highest termite pressure of any US state, with all four major termite types present",
"Formosan termite colonies can contain 5-10 million individuals — 10x larger than native subterranean colonies",
"The Southeast accounts for over 50% of all US termite damage claims annually"
]
},
# ... entries for ALL pest × region combinations
}
Every pest × region combination must have a unique entry. For combinations where a pest is not naturally prevalent in a region, the content should still be unique and informative (e.g., “While scorpions are rare in the Northeast, the occasional Eastern Bark Scorpion can be found in southern Pennsylvania…”).
Minimum data per entry: intro (2-4 sentences, 40+ words), species (2-3 items), seasonal_activity (4 seasons), prevention_tips (4-5 items), facts (3 items).
collections/_pest_regions/ directory if it doesn’t exist.md files in collections/_pest_regions/ before generating (idempotency)collections/_pest_regions/{pest_id}-{region_id}.md with:Front matter:
---
layout: pest-region
title: "{Pest Name} Control in the {Region Name}"
description: "How to identify, prevent, and treat {pest_name_lower} in {first_three_states} and other {Region Name} states. Region-specific {pest_name_lower} control advice."
pest_id: "{pest_id}"
region_id: "{region_id}"
pest_name: "{Pest Name}"
region_name: "{Region Name}"
permalink: /pests/{pest_id}/{region_id}/
states: "{states from region file}"
key_pests: {key_pests list from region file}
amazon_search: "{pest_name_lower} control {region_name_lower}"
---
Body content (markdown): ```markdown
{intro from PEST_REGION_DATA}
| Season | What to Expect |
|---|---|
| Spring | {spring text} |
| Summer | {summer text} |
| Fall | {fall text} |
| Winter | {winter text} |
{bulleted list of species}
{numbered list of prevention tips}
{bulleted list of facts}
For more regional home care advice, visit our sister sites:
{sister site links with UTM params}
Sister site links must use this format:
```markdown
- [Lush Lawns](https://lushlawnsbook.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal) — Complete lawn care guides
- [Harvest Home Guides](https://harvesthomeguides.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal) — Home gardening & harvest tips
- [MowGuide](https://mowguide.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal) — Mowing schedules & equipment reviews
- [Gardening by Zone](https://gardeningbyzone.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal) — Zone-specific planting guides
- [HomeFix by Zone](https://homefixbyzone.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal) — Regional home maintenance guides
Generated {N} pest-region pages in collections/_pest_regions/
Pests: {comma-separated list}
Regions: {comma-separated list}
collections/_pests/*.md files exist with front matter containing pest_id and titlecollections/_regions/*.md files exist with front matter containing region_id, title, states, key_pestscollections/_pest_regions/ directory created (if not exists).md file per pest × region combination: {pest_id}-{region_id}.md--- delimiterspython3 generate-pest-region-pages.py runs without errors from repo rootls collections/_pest_regions/*.md | wc -l outputs 88 (before T04) or 128 (after T04)python3 -c "import re; [print(f) for f in __import__('glob').glob('collections/_pest_regions/*.md') if '---' not in open(f).read()[:10]]" outputs nothinglayout: pest-region in front matterpermalink: in front mattertag=epmlabs-20 in bodyutm_source=regionalpestguide in bodydescription values: grep -h "^description:" collections/_pest_regions/*.md | sort | uniq -d | wc -l outputs 0python3 generate-pest-region-pages.py && md5sum collections/_pest_regions/*.md > /tmp/run1.md5 && python3 generate-pest-region-pages.py && md5sum -c /tmp/run1.md5 (all OK)0.md stub from _pests/ does not produce any intersection pagesgenerate-pest-region-pages.pycollections/_pest_regions/ directory (created by script)collections/_pest_regions/ (128 after T04 re-run)cd /Users/evanmaxon/repos/regionalpestguide && \
python3 -c "import ast; ast.parse(open('generate-pest-region-pages.py').read())" && \
python3 generate-pest-region-pages.py && \
echo "--- File count ---" && \
ls collections/_pest_regions/*.md | wc -l && \
echo "--- Layout check ---" && \
grep -L "layout: pest-region" collections/_pest_regions/*.md | wc -l && \
echo "--- Affiliate check ---" && \
grep -rL "epmlabs-20" collections/_pest_regions/ | wc -l && \
echo "--- Duplicate description check ---" && \
grep -h "^description:" collections/_pest_regions/*.md | sort | uniq -d | wc -l && \
echo "--- Permalink check ---" && \
grep -L "permalink:" collections/_pest_regions/*.md | wc -l
Expected: syntax OK, file count = 88 (or 128 after T04), layout missing = 0, affiliate missing = 0, duplicate descriptions = 0, permalink missing = 0.
pyyaml, no jinja2 — stdlib only)0.md stub pestPEST_REGION_DATA entry (use a sensible fallback if needed, but prefer explicit entries)_layouts/pest-region.html Layout_layouts/pest.html (existing — copy structure and HTML/CSS patterns)Description:
Create _layouts/pest-region.html — the layout used by all generated pest × region intersection pages. This layout extends default (via layout: default in its own front matter) and renders intersection page content with a breadcrumb, structured sections, affiliate CTAs, and cross-links.
Full layout file content:
layout: default —
States covered:
Thanks for subscribing to the Regional Pest Guide newsletter. You’ll receive:
Check your inbox to confirm your subscription. If you don’t see our email, check your spam folder.
← Back to Home · Read the Blog
</div>
</div>
</div>
</div> </section>
<hr>
<h2>Related Blog Posts</h2>
<ul>
</ul>
<hr>
<h2>Shop Control Products</h2>
<p><a href="https://www.amazon.com/s?k=&tag=epmlabs-20" target="_blank" class="button is-success is-medium">🛒 Browse Control Products for the on Amazon</a></p>
<hr>
<h2>Explore More</h2>
<div class="columns is-multiline">
<div class="column is-6">
<div class="box">
<h3 class="title is-5">Other Regions for </h3>
<ul>
</ul>
</div>
</div>
<div class="column is-6">
<div class="box">
<h3 class="title is-5">Other Pests in the </h3>
<ul>
</ul>
</div>
</div>
</div>
<hr>
<div class="notification is-light is-info">
<p><strong>🏡 More from EPM Labs:</strong></p>
<ul>
<li><a href="https://lushlawnsbook.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">Lush Lawns</a> — Complete lawn care guides</li>
<li><a href="https://harvesthomeguides.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">Harvest Home Guides</a> — Home gardening & harvest tips</li>
<li><a href="https://mowguide.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">MowGuide</a> — Mowing schedules & equipment reviews</li>
<li><a href="https://gardeningbyzone.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">Gardening by Zone</a> — Zone-specific planting guides</li>
<li><a href="https://lifestarter.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">LifeStarter</a> — Life skills & adulting guides</li>
<li><a href="https://homefixbyzone.com?utm_source=regionalpestguide&utm_medium=cross-site&utm_campaign=internal" target="_blank">HomeFix by Zone</a> — Regional home maintenance guides</li>
</ul>
</div>
</article>
</div>
</div>
</div>
</section>
**Key layout features:**
- Breadcrumb: Home → Pests → {Pest Name} → {Region Name}
- Tags showing region and pest at a glance
- States notification box
- `<section class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-8">
<div class="content">
<h1 class="title is-2">Thanks for Subscribing!</h1>
<h2 id="youre-in-">You’re in! 🎉</h2>
<p>Thanks for subscribing to the Regional Pest Guide newsletter. You’ll receive:</p>
<ul>
<li><strong>Seasonal pest prevention checklists</strong> for your region</li>
<li><strong>New article alerts</strong> when we publish guides</li>
<li><strong>Exclusive tips</strong> not available on the blog</li>
</ul>
<p>Check your inbox to confirm your subscription. If you don’t see our email, check your spam folder.</p>
<p><a href="/">← Back to Home</a> · <a href="/blog/">Read the Blog</a></p>
</div>
</div>
</div>
</div>
</section>
` renders the markdown body (intro, seasonal table, species, prevention, facts, affiliate CTA, sister links from the generated .md file)
- Related blog posts filtered by `pest_id` or `region_id` in post tags
- Amazon affiliate button using `page.amazon_search` (URL-encoded)
- Cross-navigation: "Other Regions for this Pest" and "Other Pests in this Region" boxes
- Sister site links from `site.sister_sites` config
- **Input Contract:**
- `_layouts/default.html` exists (parent layout)
- Generated pages have front matter with: `layout`, `title`, `description`, `pest_id`, `region_id`, `pest_name`, `region_name`, `permalink`, `states`, `amazon_search`
- `site.sister_sites` defined in `_config.yml`
- `site.pest_regions` collection defined in `_config.yml` (T03)
- `site.posts` with `tags` array
- **Output Contract:**
- `_layouts/pest-region.html` file that renders correctly in Jekyll
- Breadcrumb with 4 levels
- Cross-navigation boxes populated from `site.pest_regions`
- Amazon affiliate link with `epmlabs-20` tag
- Sister site links with UTM parameters
- **Acceptance Criteria:**
- [ ] File exists at `_layouts/pest-region.html`
- [ ] File starts with `---\nlayout: default\n---`
- [ ] Contains `Project Spec: RegionalPestGuide Programmatic Content Expansion` in an `<h1>` tag
- [ ] Contains breadcrumb `<nav>` with links to `/`, `/pests/`, and `/pests//`
- [ ] Contains `<section class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-8">
<div class="content">
<h1 class="title is-2">Thanks for Subscribing!</h1>
<h2 id="youre-in-">You’re in! 🎉</h2>
<p>Thanks for subscribing to the Regional Pest Guide newsletter. You’ll receive:</p>
<ul>
<li><strong>Seasonal pest prevention checklists</strong> for your region</li>
<li><strong>New article alerts</strong> when we publish guides</li>
<li><strong>Exclusive tips</strong> not available on the blog</li>
</ul>
<p>Check your inbox to confirm your subscription. If you don’t see our email, check your spam folder.</p>
<p><a href="/">← Back to Home</a> · <a href="/blog/">Read the Blog</a></p>
</div>
</div>
</div>
</div>
</section>
` to render page body
- [ ] Contains Amazon affiliate link with `tag=epmlabs-20`
- [ ] Contains `site.sister_sites` loop for cross-site links
- [ ] Contains `site.pest_regions` loops for cross-navigation
- [ ] `bundle exec jekyll build` produces zero Liquid errors referencing `pest-region` layout
- [ ] Rendered HTML for any intersection page contains a valid `<nav class="breadcrumb">` element
- [ ] Rendered HTML contains at least one `<a>` tag with `amazon.com` href
- **Dependencies:** T03 (needs `pest_regions` collection in `_config.yml` for `site.pest_regions` loops to work)
- **Complexity:** M
- **Files to create:**
- `_layouts/pest-region.html`
- **Self-check command:**
```bash
cd /Users/evanmaxon/repos/regionalpestguide && \
test -f _layouts/pest-region.html && echo "PASS: file exists" || echo "FAIL: file missing" && \
head -3 _layouts/pest-region.html | grep -q "layout: default" && echo "PASS: extends default" || echo "FAIL: no parent layout" && \
grep -q "page.title" _layouts/pest-region.html && echo "PASS: uses page.title" || echo "FAIL: missing title" && \
grep -q "epmlabs-20" _layouts/pest-region.html && echo "PASS: affiliate tag" || echo "FAIL: no affiliate tag" && \
grep -q "site.pest_regions" _layouts/pest-region.html && echo "PASS: cross-nav" || echo "FAIL: no cross-nav" && \
grep -q "site.sister_sites" _layouts/pest-region.html && echo "PASS: sister sites" || echo "FAIL: no sister sites"
default layoutpage.* or site.* variablesThanks for subscribing to the Regional Pest Guide newsletter. You’ll receive:
Check your inbox to confirm your subscription. If you don’t see our email, check your spam folder.
← Back to Home · Read the Blog
</div>
</div>
</div>
</div> </section> ` tag — the generated markdown body must render
jekyll-sitemap and jekyll-seo-tag_config.yml (existing), _layouts/pest.html (existing), _layouts/region.html (existing)Description:
Three changes required:
1. Add pest_regions collection to _config.yml:
Add pest_regions to the existing collections: block:
collections:
pests:
output: true
permalink: /pests/:slug/
regions:
output: true
permalink: /regions/:slug/
pest_regions:
output: true
permalink: /pests/:path/
Add default layout for pest_regions in the defaults: block:
- scope:
path: ""
type: "pest_regions"
values:
layout: "pest-region"
2. Update pest collection files — add region_pages: true:
For every file in collections/_pests/*.md (except 0.md), add region_pages: true to the YAML front matter. This flag is used by the updated pest.html layout to conditionally show the region cross-links section.
Example — collections/_pests/termites.md front matter becomes:
---
title: "Termites"
description: "Identify, prevent, and treat termite infestations in your home with region-specific advice."
pest_id: "termites"
emoji: "🐜"
region_pages: true
---
3. Update region collection files — add pest_pages: true:
For every file in collections/_regions/*.md, add pest_pages: true to the YAML front matter.
Example — collections/_regions/southeast.md front matter becomes:
---
title: "Southeast"
description: "Pest control for the Southeast US — hot, humid climates mean year-round pest pressure."
region_id: "southeast"
key_pests: ['termites', 'mosquitoes', 'cockroaches', 'fire ants', 'fleas']
states: "Alabama, Florida, Georgia, Louisiana, Mississippi, North Carolina, South Carolina, Tennessee, Virginia"
pest_pages: true
---
4. Add cross-links section to _layouts/pest.html:
Insert the following block before the closing </article> tag, after the existing sister sites <div class="notification"> block:
5. Add cross-links section to _layouts/region.html:
Insert the following block before the closing </article> tag, after the existing “Browse by Pest” section:
_config.yml exists with collections: and defaults: blocks_layouts/pest.html exists with the structure shown in §2.2_layouts/region.html exists with the structure shown in §2.2pest_id in front matterregion_id in front matter_config.yml includes pest_regions collection with output: true and permalink: /pests/:path/_config.yml includes default layout pest-region for pest_regions type0.md) have region_pages: true in front matterpest_pages: true in front matter_layouts/pest.html contains conditional region cross-links block_layouts/region.html contains conditional pest cross-links blockgrep -A2 "pest_regions:" _config.yml shows output: true and permalink: /pests/:path/grep "type: \"pest_regions\"" _config.yml OR grep 'type: "pest_regions"' _config.yml OR grep "type: pest_regions" _config.yml returns a match (default scope entry exists)grep -L "region_pages: true" collections/_pests/*.md outputs only collections/_pests/0.mdgrep -L "pest_pages: true" collections/_regions/*.md outputs nothing (all regions updated)grep -q "site.pest_regions" _layouts/pest.html && echo "PASS" outputs PASSgrep -q "site.pest_regions" _layouts/region.html && echo "PASS" outputs PASSbundle exec jekyll build completes with zero errors/pests/{slug}/)/regions/{slug}/)site.pest_regions variable)_config.yml_layouts/pest.html_layouts/region.htmlcollections/_pests/ants.mdcollections/_pests/bed-bugs.mdcollections/_pests/cockroaches.mdcollections/_pests/fleas.mdcollections/_pests/mosquitoes.mdcollections/_pests/rodents.mdcollections/_pests/spiders.mdcollections/_pests/termites.mdcollections/_pests/ticks.mdcollections/_pests/wasps.mdcollections/_regions/great-plains.mdcollections/_regions/midwest.mdcollections/_regions/mountain-west.mdcollections/_regions/northeast.mdcollections/_regions/pacific-northwest.mdcollections/_regions/southeast.mdcollections/_regions/southwest.mdcollections/_regions/west-coast.mdcd /Users/evanmaxon/repos/regionalpestguide && \
grep "pest_regions:" _config.yml && echo "PASS: collection defined" && \
grep -c "region_pages: true" collections/_pests/*.md && \
grep -c "pest_pages: true" collections/_regions/*.md && \
grep -q "site.pest_regions" _layouts/pest.html && echo "PASS: pest layout cross-links" && \
grep -q "site.pest_regions" _layouts/region.html && echo "PASS: region layout cross-links"
collections: entriespermalink patterns for pests or regions0.mdcollections/_pests/termites.md (existing — follow same front matter schema and body content structure)Description:
Add 5 new pest collection files to collections/_pests/. Each file must follow the exact front matter schema of existing pest files and include substantive body content covering identification, prevention, treatment, and an Amazon affiliate product link.
Files to create:
1. collections/_pests/centipedes-millipedes.md
---
title: "Centipedes & Millipedes"
description: "Identify, prevent, and control centipede and millipede infestations in your home with region-specific advice."
pest_id: "centipedes-millipedes"
emoji: "🐛"
region_pages: true
---
Body content must cover: house centipedes vs outdoor species, millipede vs centipede differences, moisture-driven entry, basement/bathroom hotspots, exclusion methods, dehumidifier recommendation, Amazon product CTA, and sister site cross-links. Minimum 300 words.
2. collections/_pests/earwigs-silverfish.md
---
title: "Earwigs & Silverfish"
description: "Identify, prevent, and control earwig and silverfish infestations in your home with region-specific advice."
pest_id: "earwigs-silverfish"
emoji: "🪲"
region_pages: true
---
Body content must cover: earwig identification (forceps/pincers), silverfish identification (teardrop shape, silver scales), moisture requirements, paper/fabric damage from silverfish, garden damage from earwigs, trapping methods, Amazon product CTA, and sister site cross-links. Minimum 300 words.
3. collections/_pests/stink-bugs.md
---
title: "Stink Bugs"
description: "Identify, prevent, and control stink bug infestations in your home with region-specific advice."
pest_id: "stink-bugs"
emoji: "🪲"
region_pages: true
---
Body content must cover: brown marmorated stink bug (BMSB) as invasive species, shield-shaped identification, fall home invasion behavior, overwintering in walls/attics, why not to crush them (odor), vacuum removal method, exclusion sealing, Amazon product CTA, and sister site cross-links. Minimum 300 words.
4. collections/_pests/boxelder-bugs.md
---
title: "Boxelder Bugs"
description: "Identify, prevent, and control boxelder bug infestations in your home with region-specific advice."
pest_id: "boxelder-bugs"
emoji: "🐞"
region_pages: true
---
Body content must cover: black and red/orange coloring identification, association with boxelder/maple/ash trees, fall congregating behavior on sunny walls, overwintering indoors, nuisance (not structural damage), tree removal as long-term solution, sealing entry points, Amazon product CTA, and sister site cross-links. Minimum 300 words.
5. collections/_pests/carpet-beetles.md
---
title: "Carpet Beetles"
description: "Identify, prevent, and control carpet beetle infestations in your home with region-specific advice."
pest_id: "carpet-beetles"
emoji: "🪲"
region_pages: true
---
Body content must cover: varied carpet beetle, black carpet beetle, furniture carpet beetle species, larval damage to natural fibers (wool, silk, leather), adult beetles attracted to light/flowers, signs of infestation (shed larval skins, irregular holes in fabric), thorough vacuuming as primary control, Amazon product CTA, and sister site cross-links. Minimum 300 words.
After creating all 5 files, re-run python3 generate-pest-region-pages.py to generate 40 new intersection pages (5 new pests × 8 regions), bringing the total to 128.
⚠️ IMPORTANT: Before re-running the generator, the worker executing T01 must have already added entries for all 5 new pests × 8 regions (40 new entries) to the PEST_REGION_DATA dictionary. If T01 and T04 are done by different workers, T04 worker must update the generator script to add these 40 entries before re-running.
collections/_pests/ for schema referencegenerate-pest-region-pages.py from T01 already created.md files in collections/_pests/region_pages: truetag=epmlabs-20collections/_pest_regions/ls collections/_pests/*.md | wc -l outputs 16 (11 existing + 5 new)pest_id:, title:, description:, emoji:, region_pages: true in front matterepmlabs-20 in bodyutm_source=regionalpestguide in bodyfor f in centipedes-millipedes earwigs-silverfish stink-bugs boxelder-bugs carpet-beetles; do echo "$f: $(wc -w < collections/_pests/$f.md) words"; donepython3 generate-pest-region-pages.py re-runs without errorls collections/_pest_regions/*.md | wc -l outputs 128bundle exec jekyll build completes with zero errorsregion_pages: true schema established)collections/_pests/centipedes-millipedes.mdcollections/_pests/earwigs-silverfish.mdcollections/_pests/stink-bugs.mdcollections/_pests/boxelder-bugs.mdcollections/_pests/carpet-beetles.mdgenerate-pest-region-pages.py (add 40 new PEST_REGION_DATA entries for new pests × all regions)cd /Users/evanmaxon/repos/regionalpestguide && \
for f in centipedes-millipedes earwigs-silverfish stink-bugs boxelder-bugs carpet-beetles; do \
test -f "collections/_pests/$f.md" && echo "PASS: $f exists" || echo "FAIL: $f missing"; \
grep -q "pest_id:" "collections/_pests/$f.md" && echo "PASS: $f has pest_id" || echo "FAIL: $f no pest_id"; \
grep -q "region_pages: true" "collections/_pests/$f.md" && echo "PASS: $f has region_pages" || echo "FAIL: $f no region_pages"; \
grep -q "epmlabs-20" "collections/_pests/$f.md" && echo "PASS: $f has affiliate" || echo "FAIL: $f no affiliate"; \
done && \
python3 generate-pest-region-pages.py && \
echo "Total pest-region pages: $(ls collections/_pest_regions/*.md | wc -l)"
region_pages: true flag (needed for cross-links in pest.html)_posts/2025-12-01-how-to-identify-termites-by-region.md (existing — follow same front matter schema and content quality)Description:
Add 21 new blog posts to _posts/ to bring the total from 29 to 50. Each post targets a long-tail search query combining a pest type with a region or seasonal context. Posts must be substantive (800+ words), include Amazon affiliate links, include sister site cross-links, and be tagged with relevant pest_id values so they appear in the “Related Blog Posts” sections of pest and intersection pages.
Date spread: Posts should be dated across 4 weeks starting 2026-03-18, approximately 5 posts per week (Mon–Fri). Use this schedule:
| # | Date | Filename | Title | Target Keyword | Tags (must include) |
|---|---|---|---|---|---|
| 1 | 2026-03-18 | 2026-03-18-how-to-get-rid-of-ants-in-texas-heat.md |
“How to Get Rid of Ants in Texas Heat” | ants texas summer control | [ants, southwest, texas, summer] |
| 2 | 2026-03-19 | 2026-03-19-mosquito-control-pacific-northwest.md |
“Mosquito Control in the Pacific Northwest” | mosquito control pacific northwest | [mosquitoes, pacific-northwest, prevention] |
| 3 | 2026-03-20 | 2026-03-20-termites-in-southeast-winter.md |
“Do Termites Stay Active in Southeast Winters?” | termites southeast winter activity | [termites, southeast, winter, seasonal] |
| 4 | 2026-03-21 | 2026-03-21-cockroach-prevention-apartment-living.md |
“Cockroach Prevention for Apartment Living” | cockroach prevention apartment | [cockroaches, prevention, apartment] |
| 5 | 2026-03-22 | 2026-03-22-tick-removal-and-prevention-northeast.md |
“Tick Removal and Prevention in the Northeast” | tick prevention northeast lyme | [ticks, northeast, prevention, lyme] |
| 6 | 2026-03-25 | 2026-03-25-best-spider-repellents-for-basements.md |
“Best Spider Repellents for Basements” | spider repellent basement | [spiders, prevention, products] |
| 7 | 2026-03-26 | 2026-03-26-rodent-control-in-midwest-farmhouses.md |
“Rodent Control in Midwest Farmhouses” | rodent control midwest farm | [rodents, midwest, farmhouse, exclusion] |
| 8 | 2026-03-27 | 2026-03-27-flea-control-for-dogs-and-cats.md |
“Flea Control for Dogs and Cats: A Complete Guide” | flea control dogs cats pets | [fleas, pets, treatment, prevention] |
| 9 | 2026-03-28 | 2026-03-28-wasp-nest-removal-safely.md |
“How to Remove a Wasp Nest Safely” | wasp nest removal safe | [wasps, removal, safety, diy] |
| 10 | 2026-03-29 | 2026-03-29-bed-bug-signs-hotel-travel.md |
“Bed Bug Signs Every Hotel Traveler Should Know” | bed bug signs hotel travel | [bed-bugs, travel, identification, hotel] |
| 11 | 2026-04-01 | 2026-04-01-stink-bug-invasion-fall-prevention.md |
“Stink Bug Fall Invasion: How to Keep Them Out” | stink bug fall prevention | [stink-bugs, fall, prevention, exclusion] |
| 12 | 2026-04-02 | 2026-04-02-carpet-beetle-damage-wool-clothing.md |
“Carpet Beetle Damage: Protecting Your Wool and Fabrics” | carpet beetle wool damage | [carpet-beetles, prevention, identification] |
| 13 | 2026-04-03 | 2026-04-03-mosquito-borne-diseases-southeast.md |
“Mosquito-Borne Diseases in the Southeast US” | mosquito diseases southeast | [mosquitoes, southeast, health, diseases] |
| 14 | 2026-04-04 | 2026-04-04-ant-control-great-plains-farms.md |
“Ant Control for Great Plains Farms and Homesteads” | ant control great plains | [ants, great-plains, agriculture, outdoor] |
| 15 | 2026-04-05 | 2026-04-05-scorpion-proofing-your-arizona-home.md |
“Scorpion-Proofing Your Arizona Home” | scorpion proofing arizona | [spiders, southwest, arizona, exclusion] |
| 16 | 2026-04-08 | 2026-04-08-centipede-vs-millipede-identification.md |
“Centipede vs Millipede: Identification and Control” | centipede millipede identification | [centipedes-millipedes, identification] |
| 17 | 2026-04-09 | 2026-04-09-termite-swarm-season-what-to-do.md |
“Termite Swarm Season: What to Do When You See Swarmers” | termite swarm season | [termites, spring, identification, swarm] |
| 18 | 2026-04-10 | 2026-04-10-boxelder-bugs-why-on-my-house.md |
“Boxelder Bugs: Why They’re All Over Your House” | boxelder bugs house fall | [boxelder-bugs, fall, identification] |
| 19 | 2026-04-11 | 2026-04-11-diy-vs-professional-pest-control.md |
“DIY vs Professional Pest Control: When to Call an Expert” | diy vs professional pest control | [pest-control, professional, diy, cost] |
| 20 | 2026-04-12 | 2026-04-12-rodent-proof-your-mountain-cabin.md |
“How to Rodent-Proof Your Mountain Cabin” | rodent proof mountain cabin | [rodents, mountain-west, exclusion, cabin] |
| 21 | 2026-04-13 | 2026-04-13-earwig-silverfish-bathroom-guide.md |
“Earwigs and Silverfish in Your Bathroom: Causes and Solutions” | earwig silverfish bathroom | [earwigs-silverfish, bathroom, moisture] |
Content requirements for every post:
layout: post, title, description (unique, 120-160 chars), categories (array), tags (array — must include the relevant pest_id so Related Blog Posts sections pick it up), datetag=epmlabs-20 (product recommendations woven into content)_posts/ for schema and style reference.md files in _posts/YYYY-MM-DD-slug.mdtag=epmlabs-20ls _posts/*.md | wc -l outputs 50layout: post in front mattertags: in front matterfor f in _posts/2026-03-18*.md _posts/2026-03-19*.md _posts/2026-03-2*.md _posts/2026-04-*.md; do wc -w < "$f"; done | awk '$1 < 800 {print "FAIL: under 800 words"; exit 1}'epmlabs-20: for f in _posts/2026-03-18*.md _posts/2026-03-19*.md _posts/2026-03-2[0-9]*.md _posts/2026-04-*.md; do grep -qL "epmlabs-20" "$f" && echo "FAIL: $f missing affiliate"; doneutm_source=regionalpestguide: grep -rL "utm_source=regionalpestguide" _posts/2026-03-18* _posts/2026-03-19* _posts/2026-03-2[0-9]* _posts/2026-04-* | wc -l outputs 0grep -h "^title:" _posts/*.md | sort | uniq -d | wc -l outputs 0bundle exec jekyll build completes with zero errors_posts/2026-03-18-how-to-get-rid-of-ants-in-texas-heat.md_posts/2026-03-19-mosquito-control-pacific-northwest.md_posts/2026-03-20-termites-in-southeast-winter.md_posts/2026-03-21-cockroach-prevention-apartment-living.md_posts/2026-03-22-tick-removal-and-prevention-northeast.md_posts/2026-03-25-best-spider-repellents-for-basements.md_posts/2026-03-26-rodent-control-in-midwest-farmhouses.md_posts/2026-03-27-flea-control-for-dogs-and-cats.md_posts/2026-03-28-wasp-nest-removal-safely.md_posts/2026-03-29-bed-bug-signs-hotel-travel.md_posts/2026-04-01-stink-bug-invasion-fall-prevention.md_posts/2026-04-02-carpet-beetle-damage-wool-clothing.md_posts/2026-04-03-mosquito-borne-diseases-southeast.md_posts/2026-04-04-ant-control-great-plains-farms.md_posts/2026-04-05-scorpion-proofing-your-arizona-home.md_posts/2026-04-08-centipede-vs-millipede-identification.md_posts/2026-04-09-termite-swarm-season-what-to-do.md_posts/2026-04-10-boxelder-bugs-why-on-my-house.md_posts/2026-04-11-diy-vs-professional-pest-control.md_posts/2026-04-12-rodent-proof-your-mountain-cabin.md_posts/2026-04-13-earwig-silverfish-bathroom-guide.mdcd /Users/evanmaxon/repos/regionalpestguide && \
echo "Total posts: $(ls _posts/*.md | wc -l)" && \
echo "--- Word count check (new posts) ---" && \
for f in _posts/2026-03-1[89]*.md _posts/2026-03-2*.md _posts/2026-04-*.md; do \
words=$(wc -w < "$f" 2>/dev/null || echo 0); \
if [ "$words" -lt 800 ]; then echo "FAIL: $(basename $f) = $words words"; fi; \
done && \
echo "--- Affiliate check ---" && \
grep -rL "epmlabs-20" _posts/2026-03-1[89]*.md _posts/2026-03-2*.md _posts/2026-04-*.md 2>/dev/null | wc -l && \
echo "--- Duplicate title check ---" && \
grep -h "^title:" _posts/*.md | sort | uniq -d | wc -l
pest_id values in tags array (critical for Related Blog Posts filtering)T01 (Generator Script) ──────┐
├──→ T04 (New Pests) ──→ Re-run generator
T03 (Config + Navigation) ───┤
├──→ T02 (Layout)
│
T05 (Blog Posts) ─────────────┘ (independent, can run in parallel)
Execution order:
pest_regions collection from T03region_pages pattern from T03bundle exec jekyll build after all tasks completeCritical path: T01 → T04 → build validation
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| YAML parsing without PyYAML | Generator script may fail on edge cases | Medium | Use simple key-value extraction; test against all existing files; handle single-quoted lists |
| 128 intersection pages slow Jekyll build | Build time increases significantly | Low | Jekyll handles 200+ pages fine; monitor build time |
| Duplicate content flags from search engines | SEO penalty for thin/similar pages | Medium | Every page must have unique intro, species, facts; minimum 200 words unique body content per page |
| Liquid template errors in new layout | Build failures | Low | Test layout with minimal front matter first; validate all page.* variables exist |
| Generator script data dict becomes unwieldy | 128+ entries in a single Python file | Medium | Organize PEST_REGION_DATA dict alphabetically by key tuple; add comments for each section |
| Blog post quality at scale | 21 posts × 800+ words risks thin content | Medium | Each post targets a specific long-tail query; require 4+ H2 sections; verify word count in self-check |
| Amazon affiliate links break | Revenue loss | Low | Use search URLs (not product-specific ASINs) — resilient to product changes |
| # | Question | Impact | Default if unresolved |
|---|---|---|---|
| Q1 | Should intersection pages include structured data (JSON-LD) for SEO? | Better search appearance | No — defer to future enhancement |
| Q2 | Should the generator script also create a sitemap entry or rely on jekyll-sitemap? | SEO completeness | Rely on jekyll-sitemap plugin (already configured) |
| Q3 | Should intersection pages have Open Graph / social meta tags? | Social sharing | Rely on jekyll-seo-tag plugin (already configured) |
| Q4 | Should we add region-specific key_pests data for the 5 new pest categories to region files? | Region accuracy | No — existing region key_pests arrays are fine; new pests get intersection pages regardless |
| Q5 | Should the 0.md stub in _pests/ be deleted? | Cleanliness | No — leave it; generator skips it |
| Term | Definition |
|---|---|
| Intersection page | A page targeting a specific pest + region combination (e.g., “Termite Control in the Southeast”) |
| pest_id | URL-safe slug identifier for a pest (e.g., termites, bed-bugs, centipedes-millipedes) |
| region_id | URL-safe slug identifier for a region (e.g., southeast, pacific-northwest) |
| PEST_REGION_DATA | Python dictionary in the generator script containing unique content for each pest × region combination |
| Sister site | Other EPM Labs content sites that cross-link for SEO value (Lush Lawns, Harvest Home Guides, MowGuide, Gardening by Zone, HomeFix by Zone, LifeStarter) |
| UTM parameters | URL parameters for tracking cross-site traffic (utm_source, utm_medium, utm_campaign) |
| Affiliate tag | Amazon Associates tracking tag (epmlabs-20) appended to all Amazon links |
| Collection | Jekyll content type with its own directory, layout defaults, and output settings |
| Programmatic SEO | Strategy of generating many targeted pages algorithmically to capture long-tail search queries |
| GBZ | Gardening by Zone — sister site with the template generator pattern this spec follows |
| Idempotent | Running the generator multiple times produces the same output (delete-and-regenerate pattern) |
| Bulma | CSS framework used by the Jekyll site for layout and component styling |
| _Template version: autopilot/v0.4 | Spec version: 0.1_ |