132 lines
5.6 KiB
Python
132 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Patch the two main trucking source campaigns (MCS-150 #186, Inactive USDOT
|
|
#188) coupon blocks to use the build step's on-the-fly computed prices
|
|
(coupon_price_full / coupon_price_deal) instead of the hardcoded "$79 $47" /
|
|
"$149 $89", which were only true at 40% off and would be false on the 20/30%
|
|
A/B arms.
|
|
|
|
Idempotent: re-running replaces the coupon if-block again with the same content.
|
|
Run inside the workers container on prod:
|
|
python3 scripts/patch_main_coupon_blocks.py [--dry-run]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
if ROOT not in sys.path:
|
|
sys.path.insert(0, ROOT)
|
|
|
|
from scripts import build_trucking_campaigns as b # reuse lm_api + auth
|
|
|
|
|
|
def _deal_block(service_phrase: str, extra_expiry_line: str = "") -> str:
|
|
"""Coupon-aware offer block matching the deficiency _deal_box structure:
|
|
real was/now prices when priceable, percent-only otherwise."""
|
|
priced = (
|
|
'<p style="font-size:18px;font-weight:700;color:#9a3412;margin:0 0 4px">'
|
|
f'{service_phrase} for '
|
|
'<span style="text-decoration:line-through;color:#c2410c;font-weight:600">'
|
|
'{{ .Subscriber.Attribs.coupon_price_full }}</span> '
|
|
'<span style="color:#15803d">{{ .Subscriber.Attribs.coupon_price_deal }}</span>.</p>'
|
|
)
|
|
unpriced = (
|
|
'<p style="font-size:18px;font-weight:700;color:#9a3412;margin:0 0 4px">'
|
|
f'{service_phrase}.</p>'
|
|
)
|
|
expiry = "Expires {{ .Subscriber.Attribs.coupon_expires }}."
|
|
if extra_expiry_line:
|
|
expiry += " " + extra_expiry_line
|
|
return (
|
|
'{{ if .Subscriber.Attribs.coupon_code }}\n'
|
|
'<div style="background:#fff7ed;border:2px solid #f97316;border-radius:10px;'
|
|
'padding:20px;margin:20px 0;text-align:center">\n'
|
|
'<p style="font-size:13px;font-weight:700;color:#9a3412;letter-spacing:.04em;'
|
|
'margin:0 0 6px">TODAY ONLY - {{ .Subscriber.Attribs.coupon_pct }}% OFF OUR SERVICE FEE</p>\n'
|
|
'{{ if .Subscriber.Attribs.coupon_priceable }}' + priced + '{{ else }}' + unpriced + '{{ end }}\n'
|
|
'<p style="font-size:14px;color:#9a3412;margin:0 0 4px">Use code '
|
|
'<strong style="font-size:16px;letter-spacing:.08em">{{ .Subscriber.Attribs.coupon_code }}</strong> '
|
|
'(already applied when you click below).</p>\n'
|
|
f'<p style="font-size:12px;color:#b91c1c;font-weight:700;margin:0">{expiry}</p>\n'
|
|
'</div>\n'
|
|
)
|
|
|
|
|
|
# Per-campaign: the new coupon if-block (deal branch + original else branch).
|
|
BLOCKS = {
|
|
186: (
|
|
_deal_block("We file your MCS-150 update")
|
|
+ '{{ else }}\n'
|
|
'<div style="background:#f0fdf4;border:2px solid #86efac;border-radius:10px;'
|
|
'padding:20px;margin:20px 0;text-align:center">\n'
|
|
'<p style="font-size:18px;font-weight:700;color:#166534;margin:0 0 4px">'
|
|
'We can file your MCS-150 update for you.</p>\n'
|
|
'<p style="font-size:14px;color:#15803d;margin:0">No Login.gov needed. '
|
|
'No government portals. We handle everything.</p>\n'
|
|
'</div>\n'
|
|
'{{ end }}'
|
|
),
|
|
188: (
|
|
_deal_block("Reactivate your USDOT", "Reactivation takes as few as 5 business days.")
|
|
+ '{{ else }}\n'
|
|
'<div style="background:#f0fdf4;border:2px solid #86efac;border-radius:10px;'
|
|
'padding:20px;margin:20px 0;text-align:center">\n'
|
|
'<p style="font-size:18px;font-weight:700;color:#166534;margin:0 0 4px">'
|
|
'Reactivation takes as few as 5 business days.</p>\n'
|
|
'<p style="font-size:14px;color:#15803d;margin:0">We file electronically with '
|
|
'FMCSA - no Login.gov account needed.</p>\n'
|
|
'</div>\n'
|
|
'{{ end }}'
|
|
),
|
|
}
|
|
|
|
_BLOCK_RE = re.compile(
|
|
r"\{\{\s*if\s+\.Subscriber\.Attribs\.coupon_code\s*\}\}.*?\{\{\s*end\s*\}\}",
|
|
re.DOTALL,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--dry-run", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
for cid, new_block in BLOCKS.items():
|
|
camp = b.get_base_campaign(cid)
|
|
body = camp.get("body") or ""
|
|
matches = _BLOCK_RE.findall(body)
|
|
if len(matches) != 1:
|
|
print(f" [#{cid}] SKIP: found {len(matches)} coupon if-blocks (expected 1)")
|
|
continue
|
|
new_body, n = _BLOCK_RE.subn(new_block, body)
|
|
if "$47" in new_block or "$89" in new_block or "$79" in new_block or "$149" in new_block:
|
|
print(f" [#{cid}] ABORT: new block still contains a hardcoded price")
|
|
continue
|
|
# Sanity: no hardcoded coupon price survives in the rewritten coupon area.
|
|
if args.dry_run:
|
|
print(f" [#{cid}] DRY-RUN would update body ({len(body)} -> {len(new_body)} chars)")
|
|
continue
|
|
payload = {
|
|
"name": camp.get("name"),
|
|
"subject": camp.get("subject"),
|
|
"lists": [l["id"] for l in camp.get("lists", []) if isinstance(l, dict)] or [8],
|
|
"from_email": camp.get("from_email"),
|
|
"type": camp.get("type") or "regular",
|
|
"content_type": camp.get("content_type") or "html",
|
|
"body": new_body,
|
|
"altbody": camp.get("altbody"),
|
|
"template_id": camp.get("template_id") or 6,
|
|
"tags": camp.get("tags") or [],
|
|
"messenger": camp.get("messenger") or "email",
|
|
"headers": camp.get("headers") or [],
|
|
}
|
|
b.lm_api(f"/campaigns/{cid}", payload, "PUT")
|
|
print(f" [#{cid}] updated coupon block (body {len(body)} -> {len(new_body)} chars)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|