#!/usr/bin/env python3 """ Generate the public "Canadian Wholesale Carrier & Vendor Reference Guide" PDF. This is the lead-magnet attached (as a hosted download link) to the CRTC USF-increase campaign. It is the marketing-facing, generic version of the per-client vendor guide in scripts/document_gen/templates/crtc_vendor_guide_generator.py -- it reuses the SAME vetted vendor list (one source of truth) but renders a branded, standalone PDF with reportlab (no per-client entity name) suitable for hosting at site/public/guides/canada-carrier-guide.pdf. Usage: python3 scripts/generate_canada_carrier_guide_pdf.py python3 scripts/generate_canada_carrier_guide_pdf.py --out path/to/file.pdf """ from __future__ import annotations import argparse import os import sys from datetime import datetime from reportlab.lib.colors import HexColor from reportlab.lib.enums import TA_CENTER, TA_LEFT from reportlab.lib.pagesizes import letter from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet from reportlab.lib.units import inch from reportlab.platypus import ( HRFlowable, Image, ListFlowable, ListItem, Paragraph, SimpleDocTemplate, Spacer, ) # Reuse the canonical vendor list so the public guide and the per-client binder # guide never drift. Load the module BY PATH to avoid importing the # scripts.document_gen package __init__ (which pulls in jinja2/docx and other # heavy deps we don't need just for the VENDORS list). import importlib.util as _ilu _VENDOR_MODULE = os.path.join( os.path.dirname(os.path.abspath(__file__)), "document_gen", "templates", "crtc_vendor_guide_generator.py", ) _spec = _ilu.spec_from_file_location("_crtc_vendor_guide", _VENDOR_MODULE) _mod = _ilu.module_from_spec(_spec) # type: ignore[arg-type] _spec.loader.exec_module(_mod) # type: ignore[union-attr] VENDORS = _mod.VENDORS NAVY = HexColor("#1A2744") GRAY = HexColor("#64748B") SLATE = HexColor("#374151") LINK = HexColor("#1E40AF") RED = HexColor("#E63F2A") GREEN = HexColor("#059669") DEFAULT_OUT = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "site", "public", "guides", "canada-carrier-guide.pdf", ) LOGO = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "site", "public", "images", "logo.png", ) def _styles() -> dict: base = getSampleStyleSheet() return { "title": ParagraphStyle("title", parent=base["Title"], fontName="Helvetica-Bold", fontSize=20, textColor=NAVY, alignment=TA_CENTER, spaceAfter=4), "subtitle": ParagraphStyle("subtitle", parent=base["Normal"], fontName="Helvetica", fontSize=11, textColor=GRAY, alignment=TA_CENTER, spaceAfter=2), "date": ParagraphStyle("date", parent=base["Normal"], fontName="Helvetica", fontSize=10, textColor=GRAY, alignment=TA_CENTER, spaceAfter=14), "h2": ParagraphStyle("h2", parent=base["Heading2"], fontName="Helvetica-Bold", fontSize=14, textColor=NAVY, spaceBefore=10, spaceAfter=6), "body": ParagraphStyle("body", parent=base["Normal"], fontName="Helvetica", fontSize=10, textColor=SLATE, leading=15, spaceAfter=8, alignment=TA_LEFT), "intro_small": ParagraphStyle("intro_small", parent=base["Normal"], fontName="Helvetica-Oblique", fontSize=9, textColor=GRAY, leading=13, spaceAfter=8), "vname": ParagraphStyle("vname", parent=base["Normal"], fontName="Helvetica-Bold", fontSize=13, textColor=NAVY, spaceBefore=12, spaceAfter=1), "vweb": ParagraphStyle("vweb", parent=base["Normal"], fontName="Helvetica", fontSize=9, textColor=LINK, spaceAfter=3), "vsvc": ParagraphStyle("vsvc", parent=base["Normal"], fontName="Helvetica", fontSize=9.5, textColor=SLATE, leading=13, spaceAfter=2), "vnotes": ParagraphStyle("vnotes", parent=base["Normal"], fontName="Helvetica", fontSize=9, textColor=GRAY, leading=12, spaceAfter=2), "disc": ParagraphStyle("disc", parent=base["Normal"], fontName="Helvetica-Oblique", fontSize=7, textColor=HexColor("#999999"), leading=10, spaceBefore=14), } def build_guide(out_path: str) -> str: s = _styles() os.makedirs(os.path.dirname(out_path), exist_ok=True) doc = SimpleDocTemplate( out_path, pagesize=letter, topMargin=0.85 * inch, bottomMargin=0.85 * inch, leftMargin=1 * inch, rightMargin=1 * inch, title="Canadian Wholesale Carrier & Vendor Reference Guide", author="Performance West Inc.", ) story: list = [] if os.path.exists(LOGO): try: img = Image(LOGO, width=1.4 * inch, height=1.18 * inch, kind="proportional") img.hAlign = "CENTER" story.append(img) story.append(Spacer(1, 8)) except Exception: pass story.append(Paragraph("Canadian Wholesale Carrier & Vendor Reference Guide", s["title"])) story.append(Paragraph("Upstream voice, DID, SIP & UCaaS partners for CRTC-registered carriers", s["subtitle"])) story.append(Paragraph(datetime.now().strftime("%B %Y"), s["date"])) story.append(HRFlowable(width="100%", thickness=2, color=RED, spaceAfter=12)) story.append(Paragraph( "As a CRTC-registered Canadian telecommunications service provider, you will need " "upstream wholesale partners for voice termination, DID numbers, SIP trunking, and " "(optionally) white-label UCaaS or broadband. This guide lists vendors commonly used " "by Canadian telecom resellers so you can hit the ground running once your carrier " "entity is live.", s["body"])) story.append(Paragraph( "Many large US carriers and wholesale providers (Lumen, Bandwidth, Telnyx, Sinch, etc.) " "will board CRTC-registered Canadian carriers for cross-border voice and data. If you " "have a specific US carrier you want to work with, contact them directly and ask about " "their requirements for onboarding a Canadian wholesale partner -- some require an RMD " "filing, others only need a CRTC registration letter and proof of incorporation.", s["body"])) story.append(Paragraph( "Performance West does not endorse or guarantee any of these vendors. This list is " "provided for reference only. Evaluate each provider against your own traffic volume, " "coverage, and pricing requirements.", s["intro_small"])) story.append(HRFlowable(width="100%", thickness=0.75, color=HexColor("#E5E7EB"), spaceBefore=6, spaceAfter=2)) story.append(Paragraph("Recommended Canadian Wholesale Partners", s["h2"])) for v in VENDORS: story.append(Paragraph(f'{v["name"]}  —  {v["location"]}', s["vname"])) story.append(Paragraph(v["website"], s["vweb"])) story.append(Paragraph(f'Services: {v["services"]}', s["vsvc"])) story.append(Paragraph(f'Notes: {v["notes"]}', s["vnotes"])) story.append(Spacer(1, 10)) story.append(HRFlowable(width="100%", thickness=2, color=NAVY, spaceBefore=6, spaceAfter=8)) story.append(Paragraph( 'Ready to launch your Canadian carrier? Performance West handles the complete ' 'turnkey setup -- BC incorporation, CRTC domestic + BITS registration, registered ' 'office, .ca domain, Canadian DID, and corporate binder. Learn more at ' 'performancewest.net/order/canada-crtc or call ' '1-888-411-0383.', s["body"])) story.append(Paragraph( "Document prepared by Performance West Inc., a regulatory compliance consulting firm. " "Performance West Inc. is not a law firm and this document does not constitute legal " "advice or legal representation. Vendor information is current as of the date shown " "above and may change without notice.", s["disc"])) doc.build(story) print(f"[guide] wrote {out_path} ({os.path.getsize(out_path)} bytes, {len(VENDORS)} vendors)") return out_path def main() -> int: ap = argparse.ArgumentParser(description="Generate the public Canada carrier vendor guide PDF") ap.add_argument("--out", default=DEFAULT_OUT, help=f"output path (default: {DEFAULT_OUT})") args = ap.parse_args() build_guide(args.out) return 0 if __name__ == "__main__": raise SystemExit(main())