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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
157
scripts/tests/add_savings_table.py
Normal file
157
scripts/tests/add_savings_table.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"""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+ | 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 | 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 • Same +1 country code • 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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue