 Spring Hill KS, 66083

  dovewebconsulting@gmail.com

  • Home
  • > Our projects

Our Projects

The Main Areas of Work


Copywriting service icon

Copywriting

SEO optimization service icon

SEO Optimization

Social media marketing service icon

Social Media Marketing

Web design service icon

Web Design

Experience Highlights


Data Analytics Portfolio


Three projects, each written in full with the question, the data, the method and the result.

EPA Fuel Economy


41,837 rows

Data cleaning and regression across 20 columns and four decades of vehicles. A routine cleaning rule would have deleted every row.

Iris Species Comparison


8 visuals

A Power BI report with cross-filtering slicers and an authored theme, built so every visual stays honest when the reader filters it.

HR Attrition Dashboard


9 tabs

A Google Sheets dashboard tracking termination and retirement attrition against 25% and 10% targets, with protected sheets.


Digital Marketing Strategist Β· measurement, technical SEO, marketing engineering

I work where marketing meets engineering: the tracking that shows what is working, the technical faults that quietly cost money, and the systems that keep running after the person who built them is gone. Selected work from a Kansas City home-services client, over the last twelve months.

πŸ“§ DoveWebConsulting@gmail.com πŸ“ž 913-439-0166 🌐 DoveWebConsulting.com πŸŽ“ M.A., Cultural Anthropology, University of Tennessee, 2016
297keywords in Google’s top 10
C→A+security grade
9 of 10recorded leads were phone taps
2,233documented decisions

πŸ” Every number on this page can be checked. The ranking figures come from the Google Search Console API, the security grades from Mozilla Observatory and a live DNS lookup anyone can repeat, and the engineering work from a dated ledger with the verification attached to each entry. Where the data did not support a claim, the claim is not here. There is no traffic growth headline on this page, because the honest series does not show one.

πŸ“ˆ A measured search baseline, and what it does not yet show

Average position 27.4 Β· 64 keywords in the top 3 Β· 297 in the top 10

The problem

Impressions were high and clicks were not. Over a trailing ninety days the site was shown 315,407 times and earned 310 clicks, a click-through rate of about a tenth of one percent. Nothing on the site recorded a lead, so there was no way to tell a good month from a lucky one.

What I did

The bulk of it was consolidation rather than creation. Legacy location pages were folded into their service hubs behind 301 redirects, which removed a set of scaled doorway pages that were a genuine penalty risk while keeping their equity. A redirect audit separated the orphaned URLs that were already resolving correctly from the block that was deliberately left alone. Title tags and the internal link structure were rebuilt around what the business actually sells. The strategy was deliberately to shed impressions that were never going to convert.

What the baseline shows

Average position 27.4 across the account, with 64 keywords already in the top three and 297 in the top ten, and the money cluster still on page three. This is one measurement, taken on 22 July 2026 over a trailing ninety days. It is a starting line and not a result, and it is quoted that way on purpose.

πŸ” How it can be checked: Google Search Console, baseline pulled 22 July 2026 over a trailing ninety days, recorded in the project tracker’s KPIs sheet. One measurement, stated as one measurement.

πŸ›‘οΈ Took the site from a security grade of C to A+ and locked down email spoofing

Mozilla Observatory C 55/100 β†’ A+ 105/100 Β· DMARC none β†’ reject

πŸ›‘οΈ SECURITY POSTURE Β· before and afterMozilla Observatory, the same public scanner anyone can run against the domain today🚫BEFORE Β· grade C55 out of 1007 of 10 tests passingno enforced email policyanyone could spoof mailfrom the domainβœ…AFTER Β· grade A+105 out of 1009 of 10 tests passingDMARC p=reject at 100%CSP nonce per request,no unsafe-inline or evalπŸ” The one remaining failure is Subresource Integrity, left off deliberately because the scripts are Google hosted and change without notice
SECURITY POSTUREBEFOREC55 / 100AFTERA+105 / 100Mozilla Observatory, the same publicscanner anyone can run today

The problem

The site scored C, 55 out of 100, on Mozilla Observatory, passing 7 of 10 tests. The domain had no enforced email authentication, so anyone could spoof mail from it.

What I did

Worked the header stack one item at a time, each gated on a measurement first. The last step was removing the unsafe-eval relaxation from the Content Security Policy, which I tested by deploying a report-only copy alongside the live one and confirming zero violations across real page loads before enforcing it. For email I parsed 223 DMARC aggregate reports covering 12 to 19 August, confirmed every legitimate source was passing, and only then moved the policy to reject.

The result

Observatory A+, 105 out of 100, 9 of 10 tests. The one remaining failure is Subresource Integrity, deliberately not used because the scripts are Google hosted and change without notice. DMARC now p=reject at 100 percent.

πŸ” How it can be checked: Verifiable from outside right now: the live response headers carry HSTS with includeSubDomains, a per-request CSP nonce with no unsafe-inline and no unsafe-eval, and a DNS lookup of the client domain's _dmarc record returns p=reject.

πŸ” Found a five month old attribution bug that had been mislabeling every Bing lead as Google

Present since March, invisible until one deliberate test

πŸ” WHY EVERY BING LEAD CAME BACK LABELED GOOGLEThe tag read the correct source, then threw it away. One guard clause, live since 9 March.πŸ“±1 ARRIVESutm_source=bingcorrectly in the URLEye: the tag reads the source2 TAG READS ITsees bing,holds it briefly🚫3 GUARD CLAUSEa stored Google click idfrom the last 90 daysexists, so DISCARDπŸ“Š4 RECORDED ASgoogle / cpcwith a campaign andkeyword never in the URLβœ… Fixed so the newest tagged arrival wins and the stale click id is cleared with its cookie. Shipped as three separately reversible versions.
BING LEAD ATTRIBUTIONBEFOREGoogleevery Bing leadAFTERBingcorrectly labeledOne guard clause, live since 9 March.Newest tagged arrival now wins

The problem

Bing spend looked like it produced almost nothing. A single lead in five months carried a Bing source, while GA4 recorded 112 paid Bing sessions and 16 paid Bing form starts on the contact page over the same period. Those two numbers could not both be true. GA4 only labels a session bing / ppc when the tracking tags are physically present in the address, because it does not infer paid Bing from a referrer and does not read the Microsoft click id, so those 112 sessions were arriving correctly tagged and something downstream was overwriting the label.

What I did

Ran one controlled test: arrived through a Bing-tagged link and watched what the lead record said. It came back google / cpc, carrying a campaign, keyword and ad id that had never been in the address. The cause was a guard clause in the tag that read an explicit source and then discarded it whenever a stored Google click id from the previous ninety days existed. A trailing branch existed to merge them but was unreachable.

The result

Rewrote it so the newest tagged arrival wins and the stale click id is cleared along with its cookie. Verified on both hostnames before publishing. Shipped as three separately reversible versions so any one could be rolled back alone.

πŸ” How it can be checked: Recorded in the ledger with the before and after tested in the same browser on the same day. I also measured the historical damage honestly and revised my own first estimate down: 66 real lead emails since March, best estimate ten to fifteen mislabeled, stated as an estimate rather than a measurement.

🎯 Caught the primary Google Ads conversion counting phone taps as form leads

On the day it was found, 9 of 10 recorded leads were phone taps

🎯 ONE TAG, TWO TRIGGERS Β· what the bidding was actually optimizing forThe lead event that Google Ads imports as the Primary conversion for form leads🚫BEFORE Β· one tag, two triggersπŸ“ form submit β†’ generate_leadπŸ“ž phone tap β†’ generate_leadone number, two meanings9 of 10 recorded leads were tapsβœ…AFTER Β· one trigger eachπŸ“ form submit β†’ generate_leadπŸ“ž phone tap β†’ button_click_calltwo numbers, two meaningsphone tracking left untouchedπŸ” Proved in Tag Manager Preview on the live site, then confirmed on real traffic the next day
PRIMARY CONVERSIONBEFORE2triggers on one tagAFTER1form submit onlyPhone taps were counted as form leadsand trained the bidding on it

The problem

One tag was attached to two triggers: the contact form and the click-to-call button. Both wrote the same event name. That event is imported into Google Ads as the Primary conversion for form leads, so the number the bidding algorithm optimizes against was mostly phone taps.

What I did

Split them at the trigger rather than touching any Google Ads conversion tag, because those are what the bidding runs on. Proved the split in Tag Manager Preview on the live site: tapping a real call button fired the phone tags and did not fire the lead tag.

The result

Once that day's analytics finalised, 9 of the 10 recorded leads were phone taps and 1 was a real form. Verified again on live traffic the following day: a genuine phone tap produced a call event and zero lead events, and the only lead recorded was a real form submission.

πŸ” How it can be checked: Before and after read out of Tag Manager version history, so both states are on the record rather than asserted. Rollback is republishing the previous version.

πŸ€– Built an AI phone agent that recognizes returning customers, and put it live

Answers the real business line Β· confirms identity in 3.8 seconds

πŸ€– HOW THE PHONE AGENT RECOGNIZES A CALLERIt confirms rather than asserts, because a phone number is a household and not a personπŸ“žCALLER IDthe number arriveswith the callπŸ”CRM LOOKUPmatched against5,717 customersβš–οΈONE MATCH?154 numbers map tomore than one personπŸ’¬ASKS, NEVER TELLSAm I speakingwith Robert?at 3.8 seconds🚫 If the number maps to more than one customer it refuses to guess, and it never reads an address aloud
HOW THE PHONE AGENT RECOGNIZES A CALLER1Caller id arrives2Look up the CRM on that number3Confirm, never assert4More than one match: refuse to guessIt never reads an address aloud

The problem

The AI receptionist treated returning customers as strangers, asking again for a name and address they had already given. Callers hung up.

What I did

Added a lookup against the CRM on caller id. It asks to confirm rather than asserting, because a phone number is a household and not a person. It refuses when one number maps to more than one customer, which is 154 numbers out of 5,717, and never reads an address aloud. Built the whole thing on a cloned shadow assistant with its own test number first, and only promoted it after a live call proved it.

The result

On the proving call the lookup fired and the agent asked to confirm the caller's name at 3.8 seconds with no dead air.

πŸ” How it can be checked: Promotion recorded with a configuration hash before and after, and a one command rollback documented alongside it.

☁️ Migrated the whole stack off cloud infrastructure in a budget cut, with nothing lost

4,010 customer profiles rebuilt Β· 3,567 historical transcripts preserved

☁️ MIGRATING OFF THE CLOUD WITHOUT LOSING THE HISTORYThe server was switched off in a budget cut. Three years of call transcripts existed nowhere else.πŸ’€SERVER OFFprofile system,attribution bridge,reporting all on itπŸ’ΎWHAT SURVIVEDthe source data wasalready on disklocallyπŸ”§DATA LAYER ONLYscoring, segmentationand pitch logic leftcompletely untouchedβœ…REBUILT4,010 profiles, 59 fields4,016 transcripts,3,567 back to 2023πŸ” A structural diff ran against the previous version before anything was saved. Verifier exit code zero.
MIGRATING OFF THE CLOUDBEFORE0server switched offAFTER4,010profiles keptStructural diff ran before any save.Verifier exit code zero

The problem

The cloud server running the intelligence stack was switched off. The customer profile system, the attribution bridge and the reporting all lived on it, and three years of call transcripts existed nowhere else.

What I did

Rebuilt the customer profile generator against data already held locally rather than restoring the server. Kept the scoring, segmentation and pitch logic untouched and replaced only the data layer. Treated the historical transcripts as the thing that must survive and merged rather than replaced.

The result

4,010 profiles with all 59 fields, and 4,016 call transcripts carried forward including 3,567 going back to 2023. A structural diff against the previous version ran before anything was saved.

πŸ” How it can be checked: Verifier exit code zero, with profile count, field count and transcript count all checked against the prior file rather than eyeballed.

πŸ—οΈ Built and running, waiting to be used

Not everything I built was switched on. These are complete systems that exist on the site or in specification today, held back by budget or by a decision that was never taken rather than by anything technical. They are here because the foundation is the part that takes the skill, and because it explains why the traffic curve on this page is flat while the ranking curve is not.

✍️ Blog engine

2,517 lines across 9 snippets, all live

A complete publishing system sits on the site today: styled article templates, automatic read-time calculation, a table of contents, search with autocomplete, archive and popular-post components, analytics events wired for every scroll and click, and the redirect map for the URL structure. It is switched on and running. What it has never been given is a content program.

πŸ“§ Email and reactivation program

designed against a real 4,010 profile base

A campaign system and a reactivation strategy built on top of the customer profile work: segmentation by recency, value and service history, with the message logic and the do-not-contact handling already specified. The audience exists and is loaded. The sends were never approved.

πŸ’° Membership program

pricing, tiers and economics modeled

A paid membership plan with tier structure, pricing and the margin arithmetic worked through, written to be handed to an owner as a decision rather than a suggestion. It has not been launched.

πŸ“ Citation and local listing correction

gaps identified, fixes specified

An audit of business listings across directories, including the ones search surfaces that the usual tools miss, with the single highest-impact fix identified: an opening-hours conflict where the site schema says one thing and the public profile says another. Specified and queued, not executed.

πŸ“ˆ Conversion event architecture

built, and then corrected

The event layer that tells the ad platforms what a lead is. This one did ship, and then I found it was counting phone taps as form submissions and fixed it. The wider event plan beyond leads was never implemented.

πŸ’‘ The pattern worth noticing: the work that shipped is measurable and improved. The work that did not ship is built and ready. Neither of those is a story about effort, they are stories about which decisions got made.

πŸ”§ How I work

Most of what follows was learned by getting something wrong first and writing down what would have caught it.

🧭 The tool that made the change never confirms it

A script reporting success is not evidence. A file edit gets re-read, a tag gets checked in Preview, an endpoint gets curled, a deploy gets fetched from outside. Every claim in this portfolio was checked from somewhere other than where the change was made.

πŸ“‘ A written correction log, not just a win log

Every mistake gets recorded with what it cost and what check would have caught it, and that check gets automated. There are 82 entries, and the most recent one is about this document. This portfolio itself was scoped down twice because the data did not support the first version of a claim.

πŸ• Numbers carry their window and their source

A zero over one day and a zero over thirty days are different claims. Most false alarms in analytics are a date range left on the wrong setting, and most fake discrepancies are two different populations being compared.

↩️ The undo is written before the change

Version number, previous value, or the single command to reverse it, recorded in the same breath as shipping. Nothing goes live without a way back.

πŸ”§ HOW I WORKThe same five steps whether it is a ranking problem, a broken conversion, or a phone system.πŸ”MEASURE FIRSTFind what the numbersactually say beforetouching anything.Most emergencies area misread.🧩FIX THE SIGNALIf the measurement iswrong, everythingbuilt on it is wrong.Attribution beforeoptimization.πŸ› οΈBUILDShip it small, behinda preview, with arollback writtenbefore the changegoes out.βœ…PROVECheck from a differentvantage than the onethat made the change.Receipts, notnarration.πŸ“šWRITE IT DOWNSo the next person,or the next me, doesnot rediscover it.2,233 entries andcounting.πŸ’‘ The differentiator is not the tooling. It is that every number above can be checked by someone who does not trust me.
HOW I WORK1Measure the thing itself2Change one thing3Re-measure from outside4Write down what would have caught it5Automate that checkEvery number above can be checked

πŸ› οΈ What I work with

πŸ“Š Measurement

GA4 + Data API · Tag Manager · Search Console · Google Ads · Microsoft Advertising · Meta · offline conversion import · server side events

πŸ› οΈ Build

PHP · WordPress at hook level · JavaScript · Python · REST integration · Cloudflare Workers · Bricks · ServiceTitan API · Housecall Pro API · 3CX telephony · Vapi voice agents

🧩 Practice

technical SEO · site architecture · conversion tracking design · attribution debugging · security headers · email authentication · documentation a stranger can use

πŸ“š The work is documented, not just described. Alongside the results above sits a 2,233 entry engineering ledger, reference documentation for twelve platforms written from live systems rather than vendor pages, and a teaching cookbook that maps every piece of code running on the site function by function, so the next person does not have to reverse engineer it.

πŸ“§ Contact

DoveWebConsulting@gmail.com

913-439-0166

DoveWebConsulting.com

M.A., Cultural Anthropology, University of Tennessee, 2016

πŸ“Š Where the numbers come from

Google Search Console API
ranking and click series

GA4 Data API
sessions and lead events

Mozilla Observatory
security grade

Security Headers
and a live DNS TXT lookup

πŸ“š Supporting documents

Engineering ledger, 2,233 entries

Code cookbook, function by function

Platform documentation, twelve systems

Full work package index

Robert N. Dove Β· compiled 23 August 2026 Β· every figure traces to Search Console, GA4, live headers, live DNS or a dated ledger

Midwest Direct Marketing Inc:  BEFORE

Midwest Direct Marketing mobile site before the redesign
Midwest Direct Marketing desktop site before the redesign

Midwest Direct Marketing Inc: AFTER

Midwest Direct Marketing mobile site after the redesign
Midwest Direct Marketing desktop site after the redesign

Perfect Image Barber Shop:  BEFORE

Perfect Image Barber Shop mobile site before the redesign
Perfect Image Barber Shop desktop site before the redesign

Perfect Image Barber Shop:  AFTER

Perfect Image Barber Shop mobile site after the redesign
Perfect Image Barber Shop desktop site after the redesign