Add Canadian Wholesale Vendor Reference Guide to CRTC binder

New DOCX generator with 8 recommended upstream providers:
Fibernetics, Iristel, Flowroute, VoIP.ms, Telnyx, SkySwitch,
Distributel, Allstream. Each with services, website, and notes.

Wired into CRTC handler Step 6a (generates before eSign pause)
and added to binder compiler default sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-05-27 11:11:47 -05:00
parent 7aec5b23cb
commit 32c1e57c5c
3 changed files with 253 additions and 0 deletions

View file

@ -0,0 +1,238 @@
"""
Generate a Canadian Wholesale Vendor Reference Guide for CRTC corporate binders.
Lists recommended upstream voice, data, DID, and UCaaS providers for
Canadian telecom resellers. Included in the "Miscellaneous" section of
the corporate binder delivered to CRTC carrier package clients.
Usage:
from scripts.document_gen.templates.crtc_vendor_guide_generator import generate_vendor_guide
path = generate_vendor_guide(
entity_name="1234567 B.C. Ltd.",
output_path="/tmp/vendor_guide.docx",
)
"""
from __future__ import annotations
import logging
from datetime import datetime
from pathlib import Path
LOG = logging.getLogger("document_gen.crtc_vendor_guide")
try:
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
except ImportError:
LOG.warning("python-docx not installed — vendor guide generation unavailable")
Document = None
NAVY = RGBColor(0x1A, 0x27, 0x44) if RGBColor else None
GRAY = RGBColor(0x64, 0x74, 0x8B) if RGBColor else None
GREEN = RGBColor(0x05, 0x96, 0x69) if RGBColor else None
VENDORS = [
{
"name": "Fibernetics",
"location": "Cambridge, ON",
"services": "Wholesale SIP trunking, hosted PBX white-label, DID origination (Canada & US), "
"wholesale voice termination, fax-over-IP. Full API for provisioning.",
"website": "fibernetics.ca",
"notes": "Popular choice for Canadian resellers. Offers white-label UCaaS platform. "
"Competitive wholesale voice rates. Canadian-owned and operated.",
},
{
"name": "Iristel",
"location": "Markham, ON",
"services": "Wholesale voice termination (70+ countries), international DID numbers, "
"MVNO solutions, SIP trunking, SMS/MMS, STIR/SHAKEN attestation services.",
"website": "iristel.com",
"notes": "One of Canada's largest independent carriers. Operates in 70+ countries. "
"Has US operations and participates in the FCC RMD. Good for international voice.",
},
{
"name": "Flowroute (Intrado)",
"location": "Seattle, WA (serves Canada)",
"services": "US and Canadian DID provisioning (number-level API), SIP trunking, "
"SMS/MMS API, E911, CNAM. Per-minute and per-channel pricing.",
"website": "flowroute.com",
"notes": "Performance West's default DID provider for CRTC packages. Excellent API. "
"Canadian numbers available in BC, ON, AB, QC area codes. US-based but serves "
"Canadian carriers seamlessly under the shared +1 numbering plan.",
},
{
"name": "VoIP.ms",
"location": "Montreal, QC",
"services": "Canadian and US DIDs, SIP trunking, pay-per-minute voice, toll-free numbers, "
"SMS, fax, E911. Self-serve portal with instant provisioning.",
"website": "voip.ms",
"notes": "Developer-friendly with extensive API. Very competitive per-minute rates. "
"Popular with small-to-mid carriers and UCaaS resellers. Canadian-owned.",
},
{
"name": "Telnyx",
"location": "Chicago, IL (global network)",
"services": "Global SIP trunking, DID provisioning (40+ countries), SMS/MMS API, "
"wireless (eSIM), storage, identity verification. Mission control portal.",
"website": "telnyx.com",
"notes": "Full-stack communications platform. Private global network with PoPs in "
"Toronto and Vancouver. Strong API-first approach. Good for carriers building "
"programmable voice/messaging products.",
},
{
"name": "SkySwitch (Sangoma)",
"location": "Huntsville, AL / Toronto, ON",
"services": "White-label UCaaS platform, hosted PBX, contact center, SIP trunking, "
"SD-WAN. Complete reseller portal with billing integration.",
"website": "skyswitch.com",
"notes": "Full white-label UC platform — you sell under your brand, they handle the "
"infrastructure. Popular with ISPs and MSPs adding voice services. "
"Owned by Sangoma (Canadian company, TSX:STC).",
},
{
"name": "Distributel",
"location": "Toronto, ON",
"services": "Wholesale internet (DSL, cable, fibre), wholesale voice, SIP trunking. "
"CLEC with TPIA access to incumbent networks.",
"website": "distributel.ca",
"notes": "One of Canada's original competitive carriers. Strong wholesale internet "
"offering. Good option if your Canadian entity needs to resell broadband "
"in addition to voice.",
},
{
"name": "Allstream (Zayo Canada)",
"location": "Toronto, ON",
"services": "Enterprise SIP trunking, MPLS, dedicated internet, SD-WAN, "
"unified communications. Fibre network across major Canadian cities.",
"website": "allstream.com",
"notes": "Enterprise-grade wholesale provider. Extensive Canadian fibre network. "
"Better suited for larger carriers or those needing dedicated circuits. "
"Owned by Zayo Group.",
},
]
def generate_vendor_guide(
entity_name: str,
output_path: str,
) -> str | None:
"""Generate the vendor reference guide DOCX."""
if Document is None:
LOG.error("python-docx not installed")
return None
doc = Document()
# Page setup
for section in doc.sections:
section.top_margin = Inches(1)
section.bottom_margin = Inches(1)
section.left_margin = Inches(1)
section.right_margin = Inches(1)
# Title
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = title.add_run("Canadian Wholesale Vendor Reference Guide")
run.font.size = Pt(18)
run.font.color.rgb = NAVY
run.font.bold = True
# Subtitle
sub = doc.add_paragraph()
sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
sr = sub.add_run(f"Prepared for {entity_name}")
sr.font.size = Pt(11)
sr.font.color.rgb = GRAY
date_p = doc.add_paragraph()
date_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
dr = date_p.add_run(datetime.now().strftime("%B %Y"))
dr.font.size = Pt(10)
dr.font.color.rgb = GRAY
# Intro
doc.add_paragraph()
intro = doc.add_paragraph()
ir = intro.add_run(
"As a newly registered Canadian telecommunications service provider, you will need "
"upstream wholesale partners to provide voice termination, DID numbers, SIP trunking, "
"and potentially white-label UCaaS or broadband services. This guide lists vendors "
"commonly used by Canadian telecom resellers."
)
ir.font.size = Pt(10)
ir.font.color.rgb = RGBColor(0x37, 0x41, 0x51)
intro2 = doc.add_paragraph()
ir2 = intro2.add_run(
"Performance West does not endorse or guarantee any of these vendors. This list is "
"provided for reference only. You should evaluate each provider based on your specific "
"business requirements, traffic volume, geographic coverage needs, and pricing."
)
ir2.font.size = Pt(9)
ir2.font.color.rgb = GRAY
ir2.font.italic = True
# Vendors
for vendor in VENDORS:
doc.add_paragraph()
# Vendor name
name_p = doc.add_paragraph()
nr = name_p.add_run(vendor["name"])
nr.font.size = Pt(13)
nr.font.color.rgb = NAVY
nr.font.bold = True
loc_r = name_p.add_run(f"{vendor['location']}")
loc_r.font.size = Pt(10)
loc_r.font.color.rgb = GRAY
# Website
web_p = doc.add_paragraph()
wr = web_p.add_run(vendor["website"])
wr.font.size = Pt(9)
wr.font.color.rgb = RGBColor(0x1E, 0x40, 0xAF)
wr.font.underline = True
# Services
svc_label = doc.add_paragraph()
sl = svc_label.add_run("Services: ")
sl.font.size = Pt(9)
sl.font.bold = True
sl.font.color.rgb = RGBColor(0x37, 0x41, 0x51)
sv = svc_label.add_run(vendor["services"])
sv.font.size = Pt(9)
sv.font.color.rgb = RGBColor(0x37, 0x41, 0x51)
# Notes
notes_p = doc.add_paragraph()
nl = notes_p.add_run("Notes: ")
nl.font.size = Pt(9)
nl.font.bold = True
nl.font.color.rgb = GRAY
nn = notes_p.add_run(vendor["notes"])
nn.font.size = Pt(9)
nn.font.color.rgb = GRAY
# Footer disclaimer
doc.add_paragraph()
doc.add_paragraph()
disc = doc.add_paragraph()
disc_r = disc.add_run(
"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."
)
disc_r.font.size = Pt(7)
disc_r.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
disc_r.font.italic = True
# Save
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(str(out))
LOG.info("Vendor guide generated: %s", out)
return str(out)

View file

@ -49,6 +49,7 @@ DEFAULT_SECTIONS = [
"Director Consent(s)", "Director Consent(s)",
"Share Structure", "Share Structure",
"Corporate Bylaws", "Corporate Bylaws",
"Wholesale Vendor Reference Guide",
"Miscellaneous", "Miscellaneous",
] ]

View file

@ -933,6 +933,20 @@ class CanadaCRTCHandler(BaseServiceHandler):
else: else:
LOG.warning("CRTC letter generation failed — will need manual creation") LOG.warning("CRTC letter generation failed — will need manual creation")
# Step 6a: Generate vendor reference guide (included in binder)
LOG.info("[Step 6a] Generating wholesale vendor reference guide")
try:
from scripts.document_gen.templates.crtc_vendor_guide_generator import generate_vendor_guide
vendor_guide_path = generate_vendor_guide(
entity_name=formation_order.entity_name or f"{formation_order.state_filing_number} B.C. Ltd.",
output_path=os.path.join(work_dir, f"vendor_reference_guide_{order_number}.docx"),
)
if vendor_guide_path:
generated_files.append(vendor_guide_path)
LOG.info("Vendor guide generated: %s", vendor_guide_path)
except Exception as exc:
LOG.warning("Vendor guide generation failed (non-fatal): %s", exc)
# ---------------------------------------------------------- # # ---------------------------------------------------------- #
# Step 6b: eSign pause # Step 6b: eSign pause
# Upload the CRTC letter PDF to MinIO, store the object key on # Upload the CRTC letter PDF to MinIO, store the object key on