new-site/scripts/tests/add_savings_table.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

157 lines
8.4 KiB
Python

"""Add savings comparison table to scheduled Listmonk campaigns."""
import json
import subprocess
API_USER = "api"
API_PASS = "6X1rKPea61N4rZ1S65Hx5zvqzbCj30F6nvEe9oVGH_Y"
LISTMONK = "http://localhost:9100"
# HTML savings table for email (inline styles, email-safe)
SAVINGS_TABLE = """
<tr><td style="padding:20px 40px;">
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="border-radius:8px;overflow:hidden;border:1px solid #e2e8f0;">
<tr>
<td style="background:#1a2744;padding:12px 16px;font-family:Arial,sans-serif;font-size:14px;font-weight:bold;color:#ffffff;text-align:center;" colspan="2">
What you're paying now vs. what you could be paying
</td>
</tr>
<tr>
<td style="background:#fef2f2;padding:12px 16px;width:50%;vertical-align:top;border-right:1px solid #e2e8f0;">
<p style="font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:#991b1b;margin:0 0 8px;">US Carrier (FCC Section 214)</p>
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="font-family:Arial,sans-serif;font-size:11px;color:#7f1d1d;">
<tr><td style="padding:2px 0;">214 filing + attorney</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$7K-$17K</td></tr>
<tr><td style="padding:2px 0;">USF contributions (36.6%)</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$12K+/yr</td></tr>
<tr><td style="padding:2px 0;">CALEA compliance</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$50K-$500K+</td></tr>
<tr><td style="padding:2px 0;">STIR/SHAKEN</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$3K-$5K/yr</td></tr>
<tr><td style="padding:2px 0;">State PUC registrations</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$2K-$5K/yr</td></tr>
<tr><td style="padding:2px 0;">499-A filing + RMD</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$1.5K/yr</td></tr>
<tr><td style="padding:2px 0;">Customer surcharges</td><td style="padding:2px 0;text-align:right;font-weight:bold;">+15-40%</td></tr>
<tr><td colspan="2" style="border-top:1px solid #fca5a5;padding:6px 0 0;font-weight:bold;color:#991b1b;">
Year 1: $58K-$525K+ &nbsp;|&nbsp; Ongoing: $23K+/yr
</td></tr>
</table>
</td>
<td style="background:#f0fdf4;padding:12px 16px;width:50%;vertical-align:top;">
<p style="font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:#166534;margin:0 0 8px;">Canadian Carrier (CRTC Registration)</p>
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="font-family:Arial,sans-serif;font-size:11px;color:#14532d;">
<tr><td style="padding:2px 0;">CRTC registration</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$3,899</td></tr>
<tr><td style="padding:2px 0;">USF contributions</td><td style="padding:2px 0;text-align:right;font-weight:bold;color:#166534;">$0</td></tr>
<tr><td style="padding:2px 0;">CALEA equivalent</td><td style="padding:2px 0;text-align:right;font-weight:bold;color:#166534;">$0</td></tr>
<tr><td style="padding:2px 0;">STIR/SHAKEN</td><td style="padding:2px 0;text-align:right;font-weight:bold;color:#166534;">$0</td></tr>
<tr><td style="padding:2px 0;">Provincial registration</td><td style="padding:2px 0;text-align:right;font-weight:bold;color:#166534;">$0</td></tr>
<tr><td style="padding:2px 0;">Annual maintenance</td><td style="padding:2px 0;text-align:right;font-weight:bold;">$349/yr</td></tr>
<tr><td style="padding:2px 0;">Customer surcharges</td><td style="padding:2px 0;text-align:right;font-weight:bold;color:#166534;">$0</td></tr>
<tr><td colspan="2" style="border-top:1px solid #86efac;padding:6px 0 0;font-weight:bold;color:#166534;">
Year 1: $3,899 &nbsp;|&nbsp; Ongoing: $349/yr
</td></tr>
</table>
</td>
</tr>
<tr>
<td colspan="2" style="background:#dcfce7;padding:12px 16px;text-align:center;">
<p style="font-family:Arial,sans-serif;font-size:16px;font-weight:bold;color:#166534;margin:0;">
Save $55,000 - $525,000+ in Year 1
</p>
<p style="font-family:Arial,sans-serif;font-size:11px;color:#15803d;margin:4px 0 0;">
Then ~$23,000/yr ongoing &bull; Same +1 country code &bull; Zero customer surcharges
</p>
</td>
</tr>
</table>
</td></tr>
"""
def api_get(path):
r = subprocess.run(["curl", "-s", "-u", f"{API_USER}:{API_PASS}", f"{LISTMONK}{path}"],
capture_output=True, text=True, timeout=10)
return json.loads(r.stdout)
def api_put(path, data):
r = subprocess.run(["curl", "-s", "-X", "PUT", "-u", f"{API_USER}:{API_PASS}",
"-H", "Content-Type: application/json", "-d", json.dumps(data),
f"{LISTMONK}{path}"], capture_output=True, text=True, timeout=10)
return json.loads(r.stdout) if r.stdout else {}
# Campaigns to update with new subject lines emphasizing savings
# Only update scheduled (not yet sent) campaigns
SUBJECT_UPDATES = {
9: "Your competitors are saving $23K/yr in regulatory costs. Here's how.",
10: "$58K-$525K in Year 1 compliance costs — or $3,899. The math.",
11: "CALEA alone costs $50K-$500K. Canadian carriers pay $0.",
12: "Last email: $55K\u2013$525K in savings \u2014 start at ~$975 with 4 payments",
15: "The $55,000+ reason 503 US carriers also registered in Canada",
16: "\"We don't need this\" — until the FCC sends the letter",
17: "No USF. No CALEA. No state PUCs. No 499-A. Here's the setup.",
18: "Last email: save $55K+ in Year 1. Start at ~$975/mo \u2014 4 easy payments.",
22: "For counsel: $58K-$525K in avoided compliance costs for your carrier clients",
23: "Referral arrangement: $300 per carrier setup, zero liability for your firm",
}
CAMPAIGNS = [9, 10, 11, 12, 15, 16, 17, 18, 22, 23]
for cid in CAMPAIGNS:
d = api_get(f"/api/campaigns/{cid}")
data = d["data"]
body = data["body"]
name = data["name"]
if "What you're paying now" in body:
print(f" SKIP {cid:3d} | {name[:55]} | already has savings table")
continue
# Insert the savings table after the CTA button
# Find the CTA button (the red button link) and insert after it
cta_markers = [
'border-radius:4px;text-decoration:none', # CTA button style
'background:#e63f2a', # CTA button background
]
inserted = False
for marker in cta_markers:
if marker in body:
# Find the end of the CTA button row (</tr> after the button)
idx = body.index(marker)
# Find the next </tr></table></td></tr> after the button
close_idx = body.find("</td></tr>", idx)
if close_idx > 0:
close_idx += len("</td></tr>")
body = body[:close_idx] + SAVINGS_TABLE + body[close_idx:]
inserted = True
break
if not inserted:
# Fallback: insert before the chat block or footer
for fallback in ["We're online", "We are online", "style=\"display:block;margin:0 auto 10px;width:70px"]:
if fallback in body:
fb_idx = body.index(fallback)
tr_start = body[:fb_idx].rfind("<tr><td")
if tr_start > 0:
body = body[:tr_start] + SAVINGS_TABLE + body[tr_start:]
inserted = True
break
if not inserted:
print(f" FAIL {cid:3d} | {name[:55]} | no insertion point found")
continue
lists = [l["id"] for l in data.get("lists", [])]
new_subject = SUBJECT_UPDATES.get(cid, data["subject"])
old_subject = data["subject"]
result = api_put(f"/api/campaigns/{cid}", {
"name": data["name"],
"subject": new_subject,
"body": body,
"lists": lists,
"content_type": data.get("content_type", "richtext"),
"type": data.get("type", "regular"),
})
if "data" in result:
subj_changed = " | subject updated" if new_subject != old_subject else ""
print(f" OK {cid:3d} | {name[:55]} | +savings table{subj_changed}")
if new_subject != old_subject:
print(f" Old: {old_subject}")
print(f" New: {new_subject}")
else:
print(f" FAIL {cid:3d} | {name[:55]} | API error")
print("\nDone")