Routes SC intrastate-authority orders to the real SCDMV COC product instead of a
PSC certificate (which doesn't apply to property carriers):
- sc_coc_filing.py: emails the carrier a one-click yes/no — does your insurer
have / can they file a Form E (SC intrastate liability, $750k or $300k by
GVWR) with SCDMV? Records the answer; builds the filled COC package.
- state_trucking._handle_sc_coc_gate: SC intrastate gate —
no answer -> email the question once, HOLD
answered no -> broker referral opened, HOLD (ops todo)
answered yes-> proceed to bill the exact $25 SCDMV COC fee (at cost) + file
- API POST /compliance-orders/:id/sc-insurance: records yes/no in intake_data
(no schema change); NO opens an insurance_lead broker-referral ticket +
Telegram; YES re-dispatches the worker to bill the $25 + file.
- site/order/sc-insurance: customer one-click yes/no page (auto-submits when
the email links straight to ?have=yes|no).
Non-SC intrastate still uses the PSC/PUC email path or a manual todo.
164 lines
7.5 KiB
Python
164 lines
7.5 KiB
Python
"""South Carolina intrastate Certificate of Compliance (COC) flow.
|
|
|
|
For-hire PROPERTY carriers based in / operating intrastate in SC register via the
|
|
SCDMV Certificate of Compliance (COC) — NOT a PSC certificate (which only covers
|
|
passenger, household-goods, and hazardous-waste-for-disposal carriers).
|
|
|
|
Compliance steps we automate for an SC intrastate property carrier:
|
|
1. Confirm the carrier's liability insurance is (or will be) filed with SCDMV
|
|
on a Form E by their INSURANCE COMPANY (SCDMV does not accept an ACORD cert).
|
|
We email a simple yes/no question with a one-click response page.
|
|
- YES -> proceed to bill the $25 COC fee + file the COC application.
|
|
- NO -> open a broker-referral ticket so we connect them with an insurer
|
|
that writes SC intrastate liability and can file the Form E.
|
|
2. Bill the exact $25 SCDMV COC new-application fee at cost (gov-fee child),
|
|
reusing the standard payment-link flow.
|
|
3. Fill + submit the SCDMV Form COC (mail w/ $25 check to Blythewood, or fax),
|
|
and confirm the Form E is on record.
|
|
|
|
Coverage class (drives whether cargo insurance / Form H is needed, and is the
|
|
basis for the state fee determination):
|
|
- E-L : low-value commodities (scrap metal, dump-truck aggregates) — Form E
|
|
liability only, no cargo insurance.
|
|
- E-LC : property properly insured for any cargo — Form E + Form H.
|
|
|
|
Insurance minimums (intrastate, non-hazmat liability):
|
|
- GVWR >= 10,000 lbs: $750,000
|
|
- GVWR < 10,000 lbs: $300,000
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import smtplib
|
|
from email.mime.text import MIMEText
|
|
|
|
LOG = logging.getLogger("workers.services.sc_coc_filing")
|
|
|
|
SITE = os.getenv("PUBLIC_SITE_URL", "https://performancewest.net").rstrip("/")
|
|
SMTP_HOST = os.getenv("SMTP_HOST", "co.carrierone.com")
|
|
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
|
SMTP_USER = os.getenv("SMTP_USER", "")
|
|
SMTP_PASS = os.getenv("SMTP_PASS", "")
|
|
SMTP_FROM = os.getenv("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
|
FROM_ADDR = "noreply@performancewest.net"
|
|
|
|
# SCDMV COC submission coordinates (from SCDMV Form COC, rev 11/2025).
|
|
SCDMV_COC = {
|
|
"mail": "SCDMV, P.O. Box 1498, Blythewood, SC 29016-0027",
|
|
"fax": "(803) 896-2698",
|
|
"phone": "(803) 896-3870",
|
|
"new_fee_usd": 25,
|
|
}
|
|
|
|
|
|
def liability_minimum_usd(intake: dict) -> int:
|
|
"""SC intrastate non-hazmat liability minimum by GVWR bracket."""
|
|
bracket = (intake.get("gross_weight_bracket") or "").lower()
|
|
# Treat unknown / 26k / 80k brackets as the >=10k bucket (the common case).
|
|
if bracket in ("under_10k", "lt_10k"):
|
|
return 300_000
|
|
return 750_000
|
|
|
|
|
|
def insurance_response_url(order_number: str, answer: str) -> str:
|
|
"""One-click yes/no link the customer clicks from the email."""
|
|
return f"{SITE}/order/sc-insurance?order={order_number}&have={answer}"
|
|
|
|
|
|
def send_insurance_question_email(order_number: str, customer_email: str,
|
|
customer_name: str, entity_name: str,
|
|
intake: dict) -> bool:
|
|
"""Email the carrier a simple yes/no: do they have SC intrastate liability
|
|
insurance their insurer can file on a Form E with SCDMV?"""
|
|
if not customer_email:
|
|
return False
|
|
minimum = liability_minimum_usd(intake)
|
|
yes_url = insurance_response_url(order_number, "yes")
|
|
no_url = insurance_response_url(order_number, "no")
|
|
body = (
|
|
f"Hi {customer_name or 'there'},\n\n"
|
|
f"We're getting your South Carolina intrastate authority set up for "
|
|
f"{entity_name}. In SC, a for-hire carrier like yours registers with the "
|
|
f"SCDMV (Certificate of Compliance) — and the one thing the state requires "
|
|
f"from your insurance company is a liability filing called a \"Form E.\"\n\n"
|
|
f"Quick question so we know how to proceed:\n\n"
|
|
f"Does your current insurance company have (or can they file) a Form E "
|
|
f"showing at least ${minimum:,} in liability coverage for South Carolina "
|
|
f"intrastate operation?\n\n"
|
|
f"(Note: this must be a Form E filed by your insurance COMPANY directly "
|
|
f"with SCDMV — a regular ACORD certificate of insurance is not accepted.)\n\n"
|
|
f"✅ YES — my insurer can file the Form E:\n{yes_url}\n\n"
|
|
f"❌ NO / NOT SURE — I need help getting the right insurance:\n{no_url}\n\n"
|
|
f"If you click NO, no problem — we'll connect you with a broker who writes "
|
|
f"SC intrastate trucking liability and can file the Form E for you.\n\n"
|
|
f"Once your Form E is on file, we complete your SCDMV Certificate of "
|
|
f"Compliance and you're cleared to operate.\n\n"
|
|
f"Order: {order_number}\n"
|
|
f"Questions? Just reply here or call (888) 411-0383.\n\n"
|
|
f"Performance West Inc.\nDOT / State Motor Carrier Compliance\n"
|
|
)
|
|
try:
|
|
msg = MIMEText(body)
|
|
msg["Subject"] = f"Quick question about your SC insurance — {entity_name}"
|
|
msg["From"] = SMTP_FROM
|
|
msg["To"] = customer_email
|
|
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as s:
|
|
s.starttls()
|
|
if SMTP_USER and SMTP_PASS:
|
|
s.login(SMTP_USER, SMTP_PASS)
|
|
s.sendmail(FROM_ADDR, [customer_email], msg.as_string())
|
|
LOG.info("[%s] SC insurance question emailed to %s", order_number, customer_email)
|
|
return True
|
|
except Exception as exc: # noqa: BLE001
|
|
LOG.error("[%s] Failed to send SC insurance question: %s", order_number, exc)
|
|
return False
|
|
|
|
|
|
def record_insurance_answer(order_number: str, have_insurance: bool) -> bool:
|
|
"""Persist the carrier's yes/no into intake_data.sc_coc_insurance. Returns
|
|
True on success. Idempotent."""
|
|
try:
|
|
import psycopg2
|
|
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"SELECT intake_data FROM compliance_orders WHERE order_number = %s",
|
|
(order_number,),
|
|
)
|
|
row = cur.fetchone()
|
|
if not row:
|
|
conn.close()
|
|
return False
|
|
intake = row[0] or {}
|
|
if isinstance(intake, str):
|
|
intake = json.loads(intake)
|
|
intake["sc_coc_insurance"] = "yes" if have_insurance else "no"
|
|
from datetime import datetime, timezone
|
|
intake["sc_coc_insurance_at"] = datetime.now(timezone.utc).isoformat()
|
|
cur.execute(
|
|
"UPDATE compliance_orders SET intake_data = %s, updated_at = now() "
|
|
"WHERE order_number = %s",
|
|
(json.dumps(intake), order_number),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
LOG.info("[%s] Recorded SC COC insurance answer: %s", order_number,
|
|
"yes" if have_insurance else "no")
|
|
return True
|
|
except Exception as exc: # noqa: BLE001
|
|
LOG.error("[%s] Failed to record insurance answer: %s", order_number, exc)
|
|
return False
|
|
|
|
|
|
def build_coc_package(order_number: str, intake: dict,
|
|
coverage_class: str | None = None) -> str | None:
|
|
"""Fill the SCDMV Form COC for this order. Returns the filled PDF path."""
|
|
try:
|
|
from scripts.document_gen.templates.sc_coc_pdf_filler import fill_sc_coc
|
|
return fill_sc_coc(intake, order_number=order_number, coverage_class=coverage_class)
|
|
except Exception as exc: # noqa: BLE001
|
|
LOG.error("[%s] Failed to build COC package: %s", order_number, exc)
|
|
return None
|