Trafilea PPC Handover
Carlos Carrero's last working day is Friday 4 September 2026. This page records what he ran, how often, and what stops running when he leaves. Part 0 is the only section a manager has to read.
0. Read this first
Nothing on this page fails loudly. No alert fires, no dashboard turns red, no report says "missing". Automations simply stop, promo assets simply keep serving, and experiments simply expire. The gap between something breaking and someone noticing is measured in weeks. That is the whole reason this document exists.
Dated obligations
| Date | What happens | Owner |
|---|---|---|
| Wed 2 Sep | Warehouse Clearance push for UK + AU (promo starts 3 Sep) | Carlos |
| Fri 4 Sep | Last day. The Mac stops running 19 scheduled Trafilea jobs, four of which write to Google Ads every morning | — |
| Tue 9 Sep | Warehouse Clearance push for US + CA (promo starts 10 Sep). The cycle needs two pushes because the script has no per-market start date | UNASSIGNED |
| Wed 24 Sep | Warehouse Clearance ends. Assets must be swapped or they keep serving | UNASSIGNED |
| Tue 30 Sep | Verdict due: SR_AU_WW_BR_ShaperMint_Core_2
Target‑IS vs Manual CPC | UNASSIGNED |
| Wed 7 Oct | Verdict due: SHM search brand Core_2 USA — Aug2026
(LowCPC_CAP) | UNASSIGNED |
| Fri 23 Oct | Verdict due: Bra_Products_CPC vs VBB |
UNASSIGNED |
What "verdict due" means. A Google Ads experiment does not wait for a decision. On its end date the arm stops and traffic returns to the original campaign, discarding whatever it learned. Two of the three below are currently winning. If nobody applies them, the account quietly reverts to the worse configuration.
Serving with no end date — act before Friday
Fourteen promo-bearing assets are enabled with no end date at all. An
asset with an empty end date serves forever; on Microsoft Ads the redemption date
(PromotionEndDate) does not stop delivery, only
Scheduling.EndDate does. The full checklist is in
reports/handover/data/MITIGATION.md. The ones customers can see today:
| Account | Asset | Why it matters |
|---|---|---|
| SHM Bing | CALLOUT "Black Friday Early Access" ×2 | Black Friday is three months away |
| SHM Bing | CALLOUT "Fall Sale in US" / "Fall Sale in CA" | No end date — will outlive the cycle |
| SHM Bing | CALLOUT "Autumn Sale in UK" | Same |
| SHM Bing | CALLOUT "Warehouse Sale" | Correct from 3 Sep, but keeps serving past 24 Sep |
| TK Bing | CALLOUT "Spring Sale" | Six months stale |
Four decisions Trafilea owes itself this week
- Who runs the bi-weekly promo cycle. One script writes to SHM Bing ×4 markets, TK Bing, SHM Google and TK Google in a single run — so it now spans all three new owners. Splitting it by account is possible but the detection keyword lists are global, and they drift apart by the second cycle. That drift is the documented cause of "EOFY Sale" serving from June into August.
- Who consolidates the weekly WoW deck. Fourteen segments, previously one person's Monday. It now splits three ways and has to converge before the deck ships.
- Which mutating automations get migrated, and which are switched off on purpose. Four jobs write to Google Ads daily from a laptop that is being returned. Letting them die silently leaves nobody knowing they ever existed.
- Who re-issues the credentials that authenticate as Carlos. Four n8n workflows keep running server-side but sign in with his tokens; every published report URL sits on his personal Cloudflare account.
1. Scope and ownership
Measured against the APIs on 1 September 2026, not from memory.
| Brand | Google Ads | Microsoft Ads |
|---|---|---|
| Shapermint | Search Brand only — ~41 enabled campaigns across US, CA, UK, AU
and OC. Accounts 1037305439 and 9328705246 |
Everything — 150000172, 4 markets, 14 active
campaigns |
| Truekind | Everything — 17 enabled: Search Brand + NB, Shopping, 2 PMax.
1871711997 |
Everything — 141454956 |
| The Spa Dr | Everything — 9 enabled: Search Brand + NB, Shopping, Demand Gen.
9662795380 |
Everything — 141948660 |
| Revel Beauty | — | Paused, zero campaigns — 141948661 |
Out of scope, and do not assume otherwise. Shapermint's Google Shopping, Performance Max, Non-Brand Search, YouTube and Demand Gen were never Carlos's. They belong to another team and are absent from this document on purpose. Every Shapermint figure quoted here covers Search Brand and Microsoft Ads only.
Who takes what
| Block | Incoming owner |
|---|---|
| SHM Search Brand — Google, 5 market groups | Juan Paiva |
| The Spa Dr — Google (Search B+NB, Shopping, Demand Gen) | Juan Paiva |
| Truekind — Google (Search B+NB, Shopping, 2 PMax) | Juan Alexander |
| All Microsoft Ads — SHM ×4, TK, TSD, Revel | Cassia |
Two processes do not split along these lines. The promo cycle and the weekly WoW deck each cross all three owners. Neither has an owner in the table above; both are listed as open decisions in Part 0. Filtering this page to one name will not show them to you, which is exactly the risk.
Built by Carlos, for other people's channels
These monitor or report on channels outside his scope. They need a decision of their own: adopt, or retire.
com.ppc.gads-bid-change-history-shm— tracks bid changes on SHM USA Non-Brand Search, Shopping and PMax- n8n SHM&TK vs Antares — Daily — covers every SHM channel and is consumed by the wider team, not just Carlos
- The SHM USA ROAS-drivers report and the
shm-pacing-leverskill — both cover Shopping and PMax
2.1 Shapermint — Google Search Brand
~41 enabled campaigns across US, CA, UK, AU and OC, in two accounts
(1037305439 for US/UK/AU, 9328705246 for Canada). The largest block
of spend in this handover.
Reading a campaign name
SR_<country>_[WW_]BR_ShaperMint_<variant>. SR is
Search, BR is brand, NB is non-brand (not yours here). The variants
each catch a different query shape:
Core_2— the main exact-match brand campaign. The single most important campaign in the account.Modifiers/Modifiers_CPC— brand plus a qualifier ("shapermint reviews", "shapermint discount").Products,Bra_Products,Cami_Products,Swimsuits_Products,Tier2_Products— brand plus a product line.Core_Broad— the broad-match arm. Structurally the weakest performer; see below.Reviews— review intent.
Bidding philosophy — read this before "fixing" anything
Brand runs on Manual CPC and Target Impression Share, managed by hand to roughly 88–90% Search Top Impression Share — not absolute impression share, which sits near 98%. There are deliberately no automated rules on these campaigns. A brand keyword that loses top-of-page to an affiliate or a competitor costs far more than the incremental click, and smart bidding has repeatedly under-bid the defensive positions. Raising this to 100% is not the goal either; the last few points of Top IS are priced irrationally.
Where it stands — 30 days to 31 August, ROAS CDP
| Market | Spend | vs prior 30d | ROAS CDP | Orders |
|---|---|---|---|---|
| USA | $82,592 | −16% | 10.39 | 12,581 |
| Canada | $25,647 | −24% | 15.70 | 5,581 |
| UK | $16,048 | +12% | 9.79 | 2,387 |
| Australia | $10,634 | +37% | 16.33 | 2,376 |
| Total | $135,367 | — | 11.80 | 22,997 |
Top Impression Share by campaign
| Campaign | Top IS | Lost to rank | 30d cost |
|---|---|---|---|
SR_US_BR_ShaperMint_Core_2 | 89.7% | 1.8% | $44,738 |
SR_US_BR_Shapermint_Bra_Products_CPC | 86.4% | 7.7% | $13,176 |
SR_US_BR_ShaperMint_Modifiers_CPC | 84.5% | 7.5% | $13,143 |
SR_CA_WW_BR_ShaperMint_Modifiers_CPC | 82.3% | 8.3% | $9,239 |
SR_UK_WW_BR_ShaperMint_Core_2 | 97.0% | 1.4% | $6,318 |
SR_CA_WW_BR_ShaperMint_Core_Broad | 74.9% | 13.3% | $6,227 |
SR_AU_WW_BR_ShaperMint_Modifiers_CPC | 71.1% | 17.0% | $3,240 |
SR_US_BR_ShaperMint_Core_Broad | 52.4% | 34.7% | $2,987 |
Three live experiments — each needs a verdict
| Experiment | Testing | Ends |
|---|---|---|
SR_AU_WW_BR_ShaperMint_Core_2 |
Target Impression Share (control) vs Manual CPC (_manualCPC_V2). Running
since 6 Aug with a daily tracking sheet | 30 Sep |
SHM search brand Core_2 USA — Aug2026 |
A low CPC cap on Core_2 (_LowCPC_CAP). The cap arm holds
90.8% Top IS at $2,701 against the control's 89.7% at $44,738 — it
has been the stronger arm on cost per click | 7 Oct |
Bra_Products_CPC vs VBB |
Target IS vs value-based bidding on Bra_Products. Launched 29 Aug — only Carlos knows it exists | 23 Oct |
The account also carries roughly 25 zombie experiments dated 2020–2024 with end dates in 2037. They belong to old video and audience tests outside this scope. Ignore them; they are noise, not risk.
Open problems and what I would do
SR_US_BR_ShaperMint_Core_Broadis losing a third of its top-of-page to rank (34.7%) at 52.4% Top IS. It is the worst-performing brand campaign in the account and has been for months. Either commit to it with a real bid and tighter negatives, or fold its budget into Core_2 and Modifiers. Half-funding a broad brand arm is the worst of both.- AU Modifiers at 71.1% Top IS, 17% lost to rank, while AU Core_2 sits at 92.8%. Australia is also the fastest-growing market (+37% spend, ROAS CDP 16.33). Modifiers is the obvious next place to buy.
- USA and Canada spend fell 16% and 24% month over month while UK and AU grew. Confirm this was deliberate pacing rather than a bid or budget drift — the bid-change history sheet covers SHM non-brand, not brand, so brand changes are not automatically logged anywhere except Notion.
- Apply the LowCPC_CAP result rather than letting it expire. It is the clearest efficiency win available and it dies on 7 October by default.
2.2 Shapermint — Microsoft Ads
Account 150000172, 4 markets. 14 active campaigns — all
Search, all brand. 148 paused Search campaigns and every Shopping, PerformanceMax and
Audience campaign are paused as of 1 September.
MSAN — the Microsoft Audience Network — must be disabled on every Search campaign. This is the first check on this account, every time, before looking at anything else. It re-enables itself through UI changes and campaign copies, and it spends Search budget on display inventory at a fraction of the ROAS.
Where it stands — 30 days to 31 August, ROAS CDP
| Segment | Spend | ROAS CDP | Orders | Note |
|---|---|---|---|---|
| Bing Search — USA | $15,430 | 8.71 | 2,063 | The core of the account |
| Bing Search — CA | $1,629 | 15.91 | 355 | Best ROAS in the account |
| Bing Search — UK | $1,302 | 6.85 | 134 | |
| Bing Search — AU | $923 | 13.38 | 156 | Underweight for its ROAS |
| Bing Max — USA | $9,116 | 1.67 | 247 | Now paused |
| Bing Max — CA | $3,876 | 0.97 | 58 | Now paused — was below break-even |
| Bing Shopping — USA | $734 | 1.07 | 15 | Now paused |
| Total | $33,010 | 6.10 | 3,028 |
Structure notes
- Every active campaign runs Enhanced CPC except
SR_UK_WW_BR_ShaperMint_Core_Experiment(Max Conversions) andSR_US_BR_ShaperMint_Core_Broad, which reports no bid strategy at all — worth a look. - Daily budgets total roughly $9,350 against ~$1,100/day of actual spend. Budget is not the constraint anywhere in this account; bids and impression share are.
- Of 448 ad extensions in the account library, only 56 are attached to the 14 active campaigns. Library status "Active" does not mean serving.
Open problems and what I would do
- Six callouts are serving with no end date, including "Black Friday Early
Access" in September and "Autumn Sale in UK". Fix these first — they are visible to
customers today. Full list in
MITIGATION.md. - Canada and Australia are underweight. ROAS CDP 15.91 and 13.38 against USA's 8.71, on $1,629 and $923 of spend. If the impression share headroom is there, these are the cheapest incremental orders in the portfolio.
- Decide whether Bing Max stays dead. It was paused at ROAS CDP 1.67 USA and 0.97 CA. That was the right call on those numbers, but nothing documents whether it is a permanent structural decision or a pause pending a fix.
- CDP is the source of truth here. Microsoft-reported ROAS is directional only; it disagrees with Tableau and always has.
2.3 Truekind
Google account 1871711997: 17 enabled campaigns across Search
Brand and Non-Brand, Shopping and two Performance Max. Microsoft account
141454956: 2 active Search campaigns, everything else paused.
Where it stands — 30 days to 31 August, ROAS CDP
| Channel | Spend | vs prior | ROAS CDP | Orders |
|---|---|---|---|---|
| Google Shopping | $25,383 | −31% | 1.96 | 735 |
| Google Performance Max | $20,310 | +8% | 1.83 | 554 |
| Google Search | $19,410 | +1% | 4.78 | 1,337 |
| Bing Shopping | $6,017 | −0% | 0.95 | 98 |
| Bing Search | $4,702 | −14% | 2.91 | 214 |
Structure notes
- Brand Search runs Manual CPC across US and CA (
Core_2,Modifiers_CPC,Products_CPC,Coupons,Reviews), withCore_Broadon Max Conversion Value. - Non-Brand:
SR_US_TK_NB_Bras_2026(Max Conv Value),SR_US_TK_NB_Competitor(tROAS), plus a broad-match experiment. - Two Performance Max campaigns, brand and non-brand, both Max Conversion Value.
- Two daily negative-keyword automations write to this account — one for NB Search, one for Shopping NB. Both stop on Friday. See Part 5.1.
Open problems and what I would do
- Bing Shopping at ROAS CDP 0.95 on $6,017. It is below break-even and it is the one line in this portfolio that loses money outright. It has since been paused — confirm that is intentional and permanent rather than an unlogged change.
SR_US_TK_NB_Bras_2026holds 10.0% Top IS on $4,724 of spend. Either it is bid far below the auction or the keyword set is too broad for its budget. This is the clearest diagnostic to run first on this account.- Google Shopping fell 31% month over month while PMax grew 8% at a lower ROAS CDP (1.83 vs 1.96). If that shift was deliberate it is moving spend the wrong way; if it was not, PMax is absorbing Shopping's inventory on its own.
- Brand Core_2 spends almost nothing ($136 in 30 days at 80.5% Top IS)
while
Products_CPCcarries $5,880. Worth confirming the brand structure is splitting queries the way it is supposed to.
2.4 The Spa Dr
The smallest account and the one with the clearest open problem. Google
9662795380: 9 enabled campaigns. Microsoft 141948660: 3 active
Search campaigns.
Where it stands — 30 days to 31 August, ROAS CDP
| Channel | Spend | vs prior | ROAS CDP | Orders |
|---|---|---|---|---|
| Google Discovery (Demand Gen) | $3,805 | +72% | 0.02 | 2 |
| Google Search | $2,811 | +30% | 4.70 | 192 |
| Google Shopping | $540 | −59% | 7.80 | 62 |
| Bing Search | $274 | −16% | 3.81 | 18 |
Demand Gen is the largest line item in the account and it produced two orders.
DGEN_US_NB_TSD_HairSerum_Discovery spent $3,805 in 30 days at ROAS CDP 0.02,
and spend grew 72% against the prior period. It is not merely
underperforming — it is scaling while underperforming. This is the first thing to
address on this account.
One caveat before cutting: Demand Gen splits across two CDP source categories —
Google Discovery carries the spend and Google Display carries some
of the orders. Both are included above and the combined picture is still two orders. The
halo argument was also tested and did not hold: break-even would require Demand Gen to be
driving 57% of all brand orders.
Structure notes
- Brand Search is Manual CPC (
CPC_Core,Modifiers,Products,Reviews_Script) plus a Max Conv Value Reviews arm.SR_US_BR_SpaDr_CPC_Coreholds 90.9% Top IS. - Non-Brand:
SR_US_NB_SpaDr_Reviews(tCPA, 65.2% Top IS) andSR_US_NB_SpaDr_Serum_Group1_Advertorial(Target Spend). - Shopping Non-Brand was paused in April 2026; its negative-keyword automation still runs against paused campaigns.
- PMax excludes traffic through a campaign-attached shared negative list
(
sharedSets/12135164772), never per-term. Always add to that same shared set.
Open problems and what I would do
- Cut or fix Demand Gen this month. $3,805 for two orders, growing. If it is being kept for reach, that decision needs to be written down somewhere and given a budget ceiling.
- Shopping fell 59% but returns ROAS CDP 7.80 — the best return in the account, shrinking. Find out why before it goes to zero.
- Google Search grew 30% at ROAS CDP 4.70. Healthy. This is where the Demand Gen budget most plausibly belongs.
- Watch for zero-conversion days. This account has a known tracking anomaly — occasional Saturdays and mid-week days with normal impression share and clicks but no conversions. Verify in GA4 or Shopify before treating one as a performance problem.
3. Operating cadence
What ran, how often, and — the column that actually matters — what happens if it is skipped. Filter by a name above and the rows narrow to that person's work. Anything automated is covered in Part 5; this is the rhythm, including the manual work that no script does.
3.1 Daily
| What | Why it exists | Time | If skipped | Owner |
|---|---|---|---|---|
| Read the SHM Search Brand daily DM | Core_2 USA vs yesterday and same-day-last-week. Brand is where a bidding mistake shows up within 24h | 5 min | A CPC or IS swing goes unseen for a day. On brand that is real money | Juan Paiva |
| Read the TSD daily DM (Google & Bing World) | Per-segment spend, orders, ROAS CDP against target, with a traffic-light | 5 min | Same — TSD is small enough that one bad day matters | Juan Paiva |
| Read the SHM & TK vs Antares DM | Actuals vs plan by brand and country. The only daily view of pacing against target | 5 min | Pacing drift compounds; by the time it is visible monthly it is too late to correct | All three |
| Check the TK negative-keyword apply ran | Miner stages candidates at 04:45/05:00, apply pushes at 08:30 | 2 min | After Friday it does not run at all — see Part 5.1 | Juan Alexander |
| Glance at promo drift notifications | Desktop alert only when drift changes. Silence is the healthy state | 0 min | This is the seven-day-wrong-landing-page failure. The watchdog dies with the Mac | Unassigned |
3.2 Weekly — Monday is the heavy day
| What | Why it exists | How long | If skipped | Owner |
|---|---|---|---|---|
| WoW slide deck — 14 segments: SHM Search Brand ×4 countries ×3 variants, SHM Bing ×4, TK Google, TK Bing | The weekly business review. Narrative, not a KPI dump: every significant change gets a root cause | ~half a day | The review happens without numbers. This is the most visible thing on the list | Unassigned — splits 3 ways |
| WoW Google Sheets companion — 10 tabs | The deck's backing detail, per channel and country | ~30 min, scripted | Deck still ships; drill-down questions have no answer | Same as the deck |
| TSD Monday WoW report (HTML) | Fully automated in n8n now — publishes and DMs the link | read only | Runs itself, until the credential dies | Juan Paiva |
| TK Non-Brand keyword expansion | Promote converting search terms into keywords. The miner removes waste; this adds reach | ~45 min | Non-brand stops growing. Slow, invisible decay | Juan Alexander |
| Microsoft Ads health pass | MSAN off on every Search campaign is the first check, every time. Then IS and rank-lost by campaign | ~30 min | MSAN silently re-enables budget into the Audience Network at poor ROAS | Cassia |
3.3 Every two weeks
| What | How long | If skipped | Owner |
|---|---|---|---|
| Promo cycle — sitelinks, promotion extensions and callouts across 6 accounts. Full detail in Part 4 | 2–4 h including the audit | Ads point at an expired or wrong collection. Customer-visible, and the documented failure has happened twice | Unassigned |
3.4 On demand
| What | When you'd do it | Owner |
|---|---|---|
| SHM brand bid proposal, split by country | When Top IS drifts off the 88–90% band. Read-only: proposes into a Sheet, never applies | Juan Paiva |
| TSD PMax / Demand Gen negative mining | When a fresh search-terms export lands. PMax excludes via a campaign-attached shared list, never per-term | Juan Paiva |
| Merchant Center feed audit | On a disapproval spike. Note the status rollup lies by country | Paiva / Alexander |
| RSA copy refresh | When ad strength drops or a promo needs new headlines. Brand RSAs stay category-neutral | Paiva / Alexander |
| Log every platform change to the Notion changelog | Always, for Trafilea accounts. Bids, budgets, pauses, negatives, ad copy, new campaigns, assets. Not for analysis or reports | All three |
4. The promo cycle
Every ~2 weeks a new promotion goes live and sitelinks, promotion extensions and callouts have to change across six accounts. It is the highest-frequency manual process in this handover and the one with the worst failure record. It also has no owner after Friday.
One script writes to all six accounts in a single run.
scripts/update_promo_extensions.py covers SHM Microsoft Ads (4 markets), TK
Microsoft Ads, SHM Google (US/AU/UK plus the separate Canada account) and TK Google. Under
the new split that means Cassia running it would mutate Juan Paiva's and Juan Alexander's
Google accounts. Running it per-account with --account is possible, but the
detection keyword lists are global — and once three people maintain them separately
they drift. That drift is the documented cause of "EOFY Sale" serving for three cycles.
4.1 The rhythm
- Marketing announces the promo in a dedicated Slack channel,
#mktcampaign_<brand>_<promo>_sale. Channels are revived, not recreated, so an old channel suddenly waking up is the signal. - Slack is the only source for the promo dates. The website never publishes them. Read them off the go-live post.
- Read the live collection URLs off each market's homepage (see 4.2).
- Edit the config block at the top of the script: name, start date, end date, per-market URLs, discount, and the detection lists.
- Dry run. Read the diff line by line.
--push, then log to the Notion changelog.- Run the post-push audit. Not optional — see 4.4.
4.2 Read the URLs off the live site every cycle
Never reuse last cycle's slug. Each market homepage embeds a "saleUrl"
naming the collection the site is currently pushing — public, no auth, no VPN:
curl -s https://shapermint.com/ | grep -o '"saleUrl":"[^"]*"' |
…and the same for /en-CA/, /en-GB/, /en-AU/ |
Then validate each candidate: the <title> must name the promo
and the page must carry a full product grid. An empty collection still
returns HTTP 200.
The suffix convention is not a convention. It changes between cycles and
between brands in the same cycle. August 2026: UK was -gb, not the
-uk of July. Canada's Labour Day was /labour-day-sale with
no country suffix, while -ca returned 200 and was empty. In the current
Warehouse Clearance cycle TK Canada flips back to the bare slug while SHM Canada keeps
-ca. Verify all six every time.
4.3 The current cycle: Warehouse Clearance, and why it needs two pushes
Verified live 1 September 2026. Discounts: SHM 70%, TK 60%.
| Market | Collection | Runs |
|---|---|---|
| SHM UK & EUR | /en-GB/collections/warehouse-sale-uk |
3–24 Sep |
| SHM AU / NZ / SG | /en-AU/collections/warehouse-sale-au |
3–24 Sep |
| SHM US | /collections/warehouse-sale | 10–24 Sep |
| SHM CA | /en-CA/collections/warehouse-sale-ca | 10–24 Sep |
| TK US | /collections/warehouse-sale | 10–24 Sep |
| TK CA | /en-CA/collections/warehouse-sale — bare slug this
cycle | 10–24 Sep |
The script supports end_date per market through
market_overrides but not start_date — it
always reads the global PROMO["start_date"]. So a staggered cycle needs two
pushes: one around 2 Sep for UK+AU with US/CA held on Labor Day via overrides, then a
second around 9 Sep to flip the base and drop the overrides. Patching
the script to take a per-market start date would remove this whole class of work
permanently, and is the single highest-value fix in this document.
4.4 The detection-list trap
Two of the three keyword lists are not URL-guarded, so any keyword that matches the current cycle's own text makes the script re-flag and re-create the assets it just made, on every run.
OLD_PROMO_URL_KEYWORDS— URL-guarded. The current cycle's slugs can safely live here.OLD_PROMO_TEXT_KEYWORDS— not guarded. Rotate every cycle.OLD_CALLOUT_KEYWORDS— never guarded, because callouts carry no URL. This list is the only detection path for them.
For this cycle: remove "warehouse sale" from both text lists before pushing,
and add "labor day" / "labour day" back as those markets leave
Labor Day. Keep "/warehouse-sale" in the URL list — that one is guarded.
Ended promo names also belong in STALE_TO_PAUSE in
apply_brand_promo_headlines.py; the two lists have drifted apart before.
4.5 The post-push audit is mandatory
The push log is blind to what the script never detected. Pull enabled
campaign_asset and customer_asset by API and assert three things:
zero prior-promo names still enabled, zero cross-country URL leaks, and every sitelink and
callout carrying a non-empty end date.
python3 scripts/handover_pull.py promo-assets then read
reports/handover/data/MITIGATION.md |
An asset with an empty end date serves forever. On Google, empty
start_date/end_date means unlimited delivery. On Microsoft Ads the
trap is sharper: PromotionEndDate is the redemption window and does not
stop delivery — only Scheduling.EndDate does. SHM Microsoft Ads currently
holds a promotion extension whose PromotionEndDate is 2018 and which is still
Active. This is exactly how "EOFY Sale" (AU) and "Canada Day Sale" (CA) served
from June into August 2026, across three cycles, undetected.
4.6 The coverage gap
The updater only swaps assets that already carry a previous promo. A campaign that never had a promo sitelink or callout is skipped forever. Campaign-level assets override account-level ones per field type, so a campaign with its own evergreen sitelinks but no promo sitelink inherits nothing from the account.
Truekind had no promo callout anywhere until August 2026. Fill gaps with
scripts/tk_promo_coverage_fill.py (dry-run by default, idempotent, reuses the
live promo sitelink rather than duplicating it) immediately after the main push. Known open
gap: SHM account 1037305439 has no account-level promo fallback, so its Shopping
US/AU and Demand Gen campaigns receive nothing.
4.7 The safety net, and that it dies on Friday
promo_drift_watchdog.py runs every two hours during working hours and answers
one question: do the landing pages our ads point at still match the promo the website is
actually running? It is read-only, notifies only when the drift changes, and it
exists because SHM UK moved to the Summer Bank Holiday Sale on 13 August 2026 and paid
search found out on the 20th — seven days of UK brand ads pointing at the wrong
collection.
It runs on the Mac. After Friday there is no watchdog. Whoever owns the promo cycle should either migrate it to n8n or add a manual mid-cycle check to their calendar.
5. Automations and continuity
Twenty Trafilea jobs are scheduled on Carlos's Mac; nineteen are loaded.
Four workflows run server-side in n8n. Every job list below was read from
launchctl and the n8n API on 1 September 2026 — not from the
workspace documentation, which has been wrong about job state before.
5.1 The four that write to Google Ads
These are not reports. They mutate live ad accounts every morning from a laptop that is being returned. Decide for each one: migrate it, or switch it off deliberately. Letting them stop on their own is the bad outcome — nothing errors, and in six months nobody remembers the automation existed.
| Job | What it writes | When | If it stops | Owner |
|---|---|---|---|---|
tk-nb-apply |
Negative keywords into 3 TK Non-Brand Search campaigns | 08:30 | Nothing breaks. Negatives stop being added, waste creeps back, nobody notices for weeks | Juan Alexander |
shp-tk-nb-apply |
Negative keywords into TK Shopping Non-Brand | 08:30 | Same — silent decay | Juan Alexander |
tsd-nb-apply |
Negative keywords into TSD Shopping Non-Brand | daily | No impact today: those campaigns are paused. Reactivating them without the miner brings the waste back | Juan Paiva |
brand-promo-auto-off |
--push: turns brand promo headlines off when a promo ends |
11:00 | Promo headlines stay live after the promo is over. This is a customer-visible failure, not a reporting one | Paiva + Alexander |
Each miner writes a staged candidate file that its apply job consumes the next morning.
The miners are read-only; only the *-apply jobs mutate. Every applied batch is
written to reports/<brand>-negatives/…-applied-YYYY-MM-DD.json, which is
the rollback record: find the negatives there and remove them in the Google Ads UI.
5.2 Runs server-side in n8n — survives the Mac, not the credentials
These four keep running after Friday. They also keep authenticating as
Carlos. When his Google and Trafilea accounts are deactivated, the Google Ads
credential fails with invalid_grant and the Antares cookie stops refreshing.
The workflows will not stop — they will run and fail, or run and publish a stale plan.
| Workflow | Schedule | Depends on |
|---|---|---|
| TSD Daily Slack Report — Google & Bing World | 04:15 COT | Google Ads OAuth credential (Carlos's refresh token), Redash, Slack |
| TSD WoW HTML — Weekly | Mon 04:00 COT | Same, plus a private GitHub publishing repository and Cloudflare Pages, both on an account outside Trafilea's control |
| SHM&TK vs Antares — Daily | 04:20 COT | Redash, the cached Antares plan, Slack. Consumed by the wider team |
| SHM&TK Antares Plan — Refresh | 03:50 COT | Antares session cookie harvested from Carlos's browser profile |
Rotating the Google Ads credential without a human re-consent is possible today via
scripts/n8n_gads_cred_rotate.py — but it injects the Mac's refresh token,
so it stops being a fix the moment the Mac is gone. The durable answer is to re-mint
both credentials under a Trafilea-owned service identity before Friday.
5.3 Reports and trackers that stop on Friday
| Job | What it produces | When | Owner |
|---|---|---|---|
shm-brand-daily | Slack DM: SHM Search Brand Core_2 USA vs yesterday and same-day-last-week | 04:00 | Juan Paiva |
shm-brand-is-tracker | SHM brand impression-share recovery tracker + Sheet | 06:00 | Juan Paiva |
shm-au-brand-is-tracker | Daily tracking sheet for the AU Target‑IS vs Manual CPC test | 04:35 | Juan Paiva |
shm-braproducts-is-tracker | Daily sheet for the Bra_Products Target‑IS test | 04:30 | Juan Paiva |
tsd-daily-slack | Redundancy layer for the n8n TSD daily. Kept loaded on purpose — on 11 Aug n8n and the Mac both failed and the catchup retry was the only reason the report shipped | 04:15 | Juan Paiva |
tsd-youtube-dashboard | Publishes the TSD YouTube impact dashboard | 06:00 | Juan Paiva |
promo-drift-watchdog | Compares live site promo collections against enabled promo assets. The safety net for the whole promo cycle | every 2h, working hours | Unassigned |
tk-nb-miner / shp-tk-nb-miner / tsd-nb-miner |
Stage negative-keyword candidates for the apply jobs | 04:45 / 05:00 | Alexander / Paiva |
wow-bing-proposal-guard | Guard for the Bing WoW proposal step | weekly | Cassia |
daily-reports-catchup | Infrastructure: retries any daily report that failed, on every wake between 07:00 and 23:00. This is why VPN outages have been survivable | on wake | — |
One discrepancy worth checking. shmtk-antares-report is
loaded on the Mac, even though the workspace documentation states it was unloaded
when the job moved to n8n. Either it is duplicating the n8n report, or the documentation is
stale. Verify before assuming which.
5.4 Tied to personal accounts — Trafilea cannot administer these
| Surface | Detail | Impact |
|---|---|---|
| Cloudflare Pages | A personal account outside Trafilea's control hosts
tsd-wow-tribe, tsd-youtube-impact, trafilea-ppc
and shm-usa-roas-drivers |
Every published report URL depends on an account Trafilea cannot administer |
| GitHub publishing repo | A private repository on a personal account; n8n PUTs the built HTML there and an Action deploys it | The TSD WoW publishing pipeline runs through a personal repo |
| Slack DM routing | SLACK_CARLOS_USER_ID is the destination of
every daily report |
Reports keep generating and land in a deactivated user's DMs |
The cheapest fix for the third row is to repoint the reports at a channel instead of a person. The first two require either transferring the projects or re-hosting them under Trafilea infrastructure.
5.5 Out of scope
The same machine runs scheduled jobs that have nothing to do with Trafilea. They are not listed here and nothing in this handover depends on them.
6. Data rules
Every rule here exists because someone got it wrong first. Each one is stated with the incident that produced it, because a rule without its reason gets dropped the first time it is inconvenient.
6.1 The CDP is the only source for business metrics
Revenue, orders, ROAS, CPA and AOV come from Redash query 2635. Never
from metrics.conversions_value or metrics.conversions. Platform
attribution undercounts and disagrees with what the team sees in Tableau. This has been
corrected more than five times.
| Metric | Source |
|---|---|
| Revenue | revenue_1dc |
| Orders | conversions_1dc |
| ROAS | revenue_1dc / spend |
| CPA | spend / conversions_1dc |
| AOV | revenue_1dc / conversions_1dc |
| Funnel | users_1d, add_to_cart_1d, initiate_checkout_1d |
| Impression Share | Platform API only — not in the CDP |
| Quality Score | Google Ads API only |
| Bids and budgets | Platform APIs only |
Say "ROAS CDP", never a bare "ROAS". The two numbers differ and readers need to know which one they are looking at.
6.2 source_category must be a list
The parameter became multi-value on 12 May 2026. Passing a bare string returns zero rows silently and any script polling for results simply times out.
Correct — {'source_category': ['Google Search']} |
Wrong — {'source_category': 'Google Search'} |
The shared helper wow_lib.redash_fetch coerces a string into a list as a
safety net. Any new script hitting 2635 directly must use the list form.
6.3 Join on campaign_id, never campaign_name
On a single day the AU brand test carried three spellings of the same
campaign in the CDP: the live name, the pre-rename form still collecting revenue, and one
with a stray space. Matching on name understated the control arm's revenue by
40%. Substring matching is worse: the control's name is a literal prefix of
the experiment's, so in silently merges both arms of a test into one number.
Query 2635 exposes campaign_id. Use it.
Rows also split by ad, adset and strategy (Acquisition and Retention). Sum them all.
6.4 Funnel definitions match Tableau, not intuition
- ATC% = add-to-carts ÷ users
- IC% = initiate-checkouts ÷ add-to-carts
- Checkout CVR = orders ÷ initiate-checkouts
The user column is users_1d. A bare users returns zero without
erroring.
6.5 The platform-vs-CDP gap is attribution, not a bug
Platform-reported conversion value runs about +4% above CDP overall, roughly +2% on brand and +20% on non-brand. Spend reconciles almost exactly. The gap is the attribution window, and it is expected. Do not "fix" it, and do not mix the two sources inside one table.
6.6 CDP events settle late
A fresh pull of the last day or two will understate add-to-carts, checkouts and orders.
Zeros in recent data usually mean lag, not a funnel collapse. Re-pull with
max_age=0 before drawing a conclusion, and prefer windows that end
yesterday rather than today.
6.7 Redash needs the VPN; the Antares MCP does not
Redash sits behind the Trafilea VPN, and VPN outages are the documented root cause of most
stale-report incidents. The antares-performance-metrics MCP tool reads the same
warehouse table over the public internet and was verified byte-identical to query 2635 on one
brand-channel-week. It is a viable escape hatch — but re-verify equality per
brand before moving any production job onto it.
6.8 Google Ads API notes that bite
- Pin REST callers to a supported version. The Python SDK negotiates its own; raw REST (all of n8n) does not. v21 was blocked progressively in August 2026 — the same query alternated success and 400 for hours, which reads like a flaky network unless you unpack the error body. A 400 is not an auth problem; a dead token returns 401.
campaign.start_datedoes not exist in v23 — it iscampaign.start_date_time.- There is no flat
campaign.target_roas. The real paths arecampaign.maximize_conversion_value.target_roasandcampaign.target_roas.target_roas. - Impression-share metrics need
campaign.advertising_channel_type = 'SEARCH'in the WHERE clause or they come back wrong on mixed-type accounts. - Validate any new GAQL before running it:
python3 scripts/lib/gads_fields.py check <resource> <fields>.campaign.name, for instance, is not selectable fromcampaign_asset.
6.9 Microsoft Ads read paths that return zero instead of erroring
Three calls in the extension API look like "this account is empty" when they are actually being read wrong. All three were hit while building this document:
GetAdExtensionIdsByAccountIdreturnsArrayOflong— read.long, notAdExtensionIdentity.GetAdExtensionsByIdsreturns.AdExtensions, plural.GetAdExtensionsAssociationstakes campaign ids asEntityIdsplusAdExtensionType— passingAdExtensionIdsraises a type error and omitting the type raises a bare SOAP fault. Never wrap it in a silentexcept.
And the one that matters operationally: Scheduling.EndDate stops
delivery; PromotionEndDate does not.
Appendix — where everything lives
The workspace
Everything runs out of a local ppc-workspace directory, mirrored to a private
GitHub repository. CLAUDE.md at the root is the operating
manual: account registry, API patterns, and a section per automation with its schedule, log
path and rollback procedure. It is long, it is current, and it is the first thing to read
after this page. Treat its claims about job state with suspicion — it has
been stale on that before.
Scripts by function
| Area | Entry point |
|---|---|
| Promo cycle | update_promo_extensions.py ·
tk_promo_coverage_fill.py · promo_drift_watchdog.py ·
auto_off_brand_promo_headlines.py |
| Negative keywords | tk_nb_search_term_miner.py /
tk_nb_apply_negatives.py · shp_tk_nb_* ·
shp_tsd_nb_* · tsd_pmax_nb_apply_negatives.py |
| Weekly WoW | wow_lib.py (shared classifiers and CDP fetch) ·
wow_sheets_generator.py · build_tsd_wow_report.py |
| Daily reports | shm_brand_daily_report.py ·
tsd_daily_slack_report.py · shmtk_antares_report.py |
| Trackers | shm_au_brand_is_tracker.py ·
shm_braproducts_is_tracker.py · shm_brand_is_recovery_tracker.py |
| Bing | shm_bing_platform_pull.py ·
bing_daily_perf_export.py · bing_get_refresh_token.py |
| Shared libraries | scripts/lib/ — config.py
(account registries), redash.py, google_ads.py,
gads_fields.py (GAQL validator), slack.py,
notion.py |
| This document | handover_pull.py (all data pulls) ·
build_handover.py |
Registries worth knowing
reports/links-registry.md— every published report URL.reports/wow-reports-registry.md— every WoW deck by week.client-context/trafilea/*.md— per-brand running context. The Spa Dr's file is the longest at ~160KB and carries most of the account's history.reference/gads-api/— 1,412 annotated Google Ads fields; query it withgads_fields.py.
The Notion changelog
Every real platform change on a Trafilea account gets logged: bid changes, pauses and
enables, budget changes, negative keywords, ad copy, new campaigns, asset additions or
removals. Not logged: analysis, reports, audits, or anything touching the
workspace itself. Database "Carlos Carrero - Changelog", id
1b5e19cdd4c580d492ddf9ac7a392567, posted through the REST API with
NOTION_CHANGELOG_TOKEN — the Notion MCP does not have access to it.
This document
Source lives in reports/handover/: content fragments in
parts/, styling and the owner filter in shell.html. Rebuild with
python3 scripts/build_handover.py. The data behind every figure is in
reports/handover/data/, regenerable with
python3 scripts/handover_pull.py {bing|is|cdp|promo-assets|creds}.
The published URL sits on a personal Cloudflare account. The single HTML file is self-contained and opens with no server — keep a copy in a Trafilea Drive folder so the content survives the link.