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:
parent
7aec5b23cb
commit
32c1e57c5c
3 changed files with 253 additions and 0 deletions
238
scripts/document_gen/templates/crtc_vendor_guide_generator.py
Normal file
238
scripts/document_gen/templates/crtc_vendor_guide_generator.py
Normal 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)
|
||||||
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue