The handler was returning the acceptance letter as an artifact, which triggered the instant-delivery email to the client before the admin placed the wholesale order with Northwest. Now uploads to MinIO but returns empty artifacts. Admin todo includes the MinIO path to send the letter after confirming the NW order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
221 lines
9.3 KiB
Python
221 lines
9.3 KiB
Python
"""D.C. Registered Agent service handler.
|
|
|
|
Most telecom carriers need a D.C. registered agent because the FCC
|
|
requires process-of-service in the District. We wholesale this through
|
|
Northwest Registered Agent (same pattern as state-level RA services per
|
|
``docs/go-live-todo.md:96``). Northwest's RA portal is login-only and
|
|
requires manual order entry; rather than scrape it, this handler creates
|
|
a well-formed admin ToDo with all the fields the Accounting Advisor
|
|
needs to place the wholesale order in under two minutes.
|
|
|
|
Once the Accounting Advisor completes the Northwest order, they record
|
|
the D.C. agent address on the telecom_entity's ``carrier_metadata``
|
|
field under ``dc_agent_address`` so subsequent compliance checkups read
|
|
green.
|
|
|
|
Flow:
|
|
1. Generate a simple acceptance-of-service PDF letter (for the customer's
|
|
records) acknowledging that Performance West has engaged Northwest
|
|
as their D.C. RA.
|
|
2. Create the admin ToDo with the wholesale order fields filled in.
|
|
3. Return the acceptance letter.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from .base_handler import BaseServiceHandler
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Northwest Registered Agent's D.C. Registered Agent address — used on
|
|
# FCC Form 499-A Lines 209-213. This is the wholesale address we place
|
|
# orders against; confirmed via the 2025 Adaptive Communications 499-A
|
|
# filing in docs/examplefilings/. Stable across all carriers we've filed.
|
|
NWRA_DC_AGENT = {
|
|
"company": "Northwest Registered Agent Service Inc.",
|
|
"street": "1717 N Street NW STE 1",
|
|
"city": "Washington",
|
|
"state": "DC",
|
|
"zip": "20036",
|
|
"phone": "509-768-2249",
|
|
"email": "support@northwestregisteredagent.com",
|
|
}
|
|
|
|
|
|
class DCAgentHandler(BaseServiceHandler):
|
|
SERVICE_SLUG = "dc-agent"
|
|
SERVICE_NAME = "D.C. Registered Agent (Annual)"
|
|
REQUIRES_LLM = False
|
|
|
|
async def process(self, order_data: dict) -> list[str]:
|
|
work_dir = self._make_work_dir()
|
|
order_number = order_data["name"]
|
|
entity = order_data.get("entity", {})
|
|
entity_id = entity.get("id")
|
|
|
|
# Generate the acceptance letter and upload to MinIO for later delivery.
|
|
# Do NOT return as artifacts — the client should only receive it AFTER
|
|
# the admin places the wholesale order with Northwest.
|
|
acceptance = self._write_acceptance_letter(order_number, entity, work_dir)
|
|
minio_path = None
|
|
if acceptance:
|
|
try:
|
|
pdf_path = self._convert_to_pdf(acceptance)
|
|
from scripts.document_gen import MinioStorage
|
|
storage = MinioStorage()
|
|
minio_key = f"compliance/{order_number}/dc_agent_acceptance.pdf"
|
|
storage.upload_file(pdf_path or acceptance, minio_key)
|
|
minio_path = minio_key
|
|
logger.info("DCAgentHandler: acceptance letter uploaded to %s", minio_key)
|
|
except Exception as exc:
|
|
logger.warning("DC agent letter upload failed: %s", exc)
|
|
|
|
# Persist the NWRA D.C. Agent address on the telecom_entity so the
|
|
# Form 499-A checklist generator can read it for Lines 209-213
|
|
# without re-computing.
|
|
if entity_id:
|
|
self._persist_dc_agent(entity_id)
|
|
|
|
self._create_wholesale_order_todo(order_number, entity, minio_path)
|
|
# Return empty — no instant delivery email. The admin sends the
|
|
# acceptance letter after placing the NW wholesale order.
|
|
return []
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# Persist NWRA D.C. agent address on the telecom_entity
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def _persist_dc_agent(self, entity_id: int) -> None:
|
|
try:
|
|
import psycopg2
|
|
|
|
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
UPDATE telecom_entities SET
|
|
dc_agent_company = %s,
|
|
dc_agent_street = %s,
|
|
dc_agent_city = %s,
|
|
dc_agent_state = %s,
|
|
dc_agent_zip = %s,
|
|
dc_agent_phone = %s,
|
|
dc_agent_email = %s
|
|
WHERE id = %s
|
|
""",
|
|
(
|
|
NWRA_DC_AGENT["company"],
|
|
NWRA_DC_AGENT["street"],
|
|
NWRA_DC_AGENT["city"],
|
|
NWRA_DC_AGENT["state"],
|
|
NWRA_DC_AGENT["zip"],
|
|
NWRA_DC_AGENT["phone"],
|
|
NWRA_DC_AGENT["email"],
|
|
entity_id,
|
|
),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
logger.info(
|
|
"DCAgentHandler: persisted NWRA D.C. agent on entity %s",
|
|
entity_id,
|
|
)
|
|
except Exception as exc:
|
|
logger.warning(
|
|
"DCAgentHandler: could not persist D.C. agent on entity %s: %s",
|
|
entity_id, exc,
|
|
)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# Acceptance letter (plain DOCX — no template)
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def _write_acceptance_letter(
|
|
self, order_number: str, entity: dict, work_dir: str
|
|
) -> str:
|
|
from docx import Document
|
|
|
|
date_str = datetime.now().strftime("%B %d, %Y")
|
|
entity_name = entity.get("legal_name", "")
|
|
dba = entity.get("dba_name", "")
|
|
display = f"{entity_name} ({dba})" if dba else entity_name
|
|
|
|
doc = Document()
|
|
doc.add_heading("D.C. Registered Agent — Acceptance of Engagement", level=1)
|
|
doc.add_paragraph(f"Date: {date_str}")
|
|
doc.add_paragraph(f"Order: {order_number}")
|
|
doc.add_paragraph(f"Client: {display}")
|
|
doc.add_paragraph(f"FRN: {entity.get('frn', 'N/A')}")
|
|
doc.add_paragraph("")
|
|
doc.add_paragraph(
|
|
"This letter confirms that Performance West Inc. has engaged "
|
|
"Northwest Registered Agent Service Inc. as D.C. Registered "
|
|
"Agent on behalf of the client identified above. Northwest "
|
|
"maintains a registered office in the District of Columbia "
|
|
"and will receive service of process on the client's behalf "
|
|
"during the engagement period (one year from the order date)."
|
|
)
|
|
doc.add_paragraph(
|
|
"D.C. registered agent address (use on FCC Form 499-A "
|
|
"Lines 209\u2013213):"
|
|
)
|
|
doc.add_paragraph(
|
|
f" {NWRA_DC_AGENT['company']}\n"
|
|
f" {NWRA_DC_AGENT['street']}\n"
|
|
f" {NWRA_DC_AGENT['city']}, {NWRA_DC_AGENT['state']} {NWRA_DC_AGENT['zip']}\n"
|
|
f" Tel: {NWRA_DC_AGENT['phone']}\n"
|
|
f" Email: {NWRA_DC_AGENT['email']}"
|
|
)
|
|
doc.add_paragraph("")
|
|
doc.add_paragraph("Performance West Inc.")
|
|
doc.add_paragraph("Regulatory Compliance Team")
|
|
|
|
out = os.path.join(
|
|
work_dir, f"dc_agent_acceptance_{order_number}.docx"
|
|
)
|
|
doc.save(out)
|
|
return out
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# Admin ToDo for wholesale order placement
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def _create_wholesale_order_todo(self, order_number: str, entity: dict, minio_path: str | None = None) -> None:
|
|
try:
|
|
from scripts.workers.erpnext_client import ERPNextClient
|
|
|
|
description = (
|
|
f"[{self.SERVICE_SLUG}] {order_number}\n\n"
|
|
f"Place a D.C. Registered Agent wholesale order with Northwest "
|
|
f"Registered Agent for this client. Once complete, record the "
|
|
f"assigned D.C. agent address on the telecom entity's "
|
|
f"carrier_metadata.dc_agent_address and send the customer a "
|
|
f"follow-up email with the address.\n\n"
|
|
f"Wholesale order fields:\n"
|
|
f" Entity legal name: {entity.get('legal_name', '')}\n"
|
|
f" DBA: {entity.get('dba_name', '')}\n"
|
|
f" FRN: {entity.get('frn', 'N/A')}\n"
|
|
f" Contact name: {entity.get('contact_name', '')}\n"
|
|
f" Contact email: {entity.get('contact_email', '')}\n"
|
|
f" Contact phone: {entity.get('contact_phone', '')}\n"
|
|
f" Carrier category: {entity.get('carrier_category', '')}\n\n"
|
|
f"Billing: use the Relay virtual debit card (SID-0002, filing fees).\n\n"
|
|
f"After placing the NW order, send the client the acceptance letter"
|
|
f"{(' (MinIO: ' + minio_path + ')') if minio_path else ''} "
|
|
f"confirming their D.C. agent is active."
|
|
)
|
|
ERPNextClient().create_resource(
|
|
"ToDo",
|
|
{
|
|
"description": description,
|
|
"priority": "Medium",
|
|
"role": "Accounting Advisor",
|
|
},
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Could not create DC agent wholesale ToDo: %s", exc)
|