feat(sc-coc): SC intrastate Certificate of Compliance flow (insurance gate -> $25 fee -> file)

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.
This commit is contained in:
justin 2026-06-16 09:15:55 -05:00
parent dae9603808
commit c46efe5730
4 changed files with 526 additions and 30 deletions

View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>South Carolina insurance | Performance West</title>
<script>
window.__PW_API = (function () {
var h = window.location.hostname;
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
return "https://api.performancewest.net";
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root { --pw600:#3a6192; --pw700:#2d4e78; --pw800:#213b5c; --pw900:#182c45;
--g100:#f3f4f6; --g200:#e5e7eb; --g300:#d1d5db; --g500:#6b7280; --g700:#374151; --g900:#111827; }
* { box-sizing: border-box; }
body { margin:0; font-family:'Inter',system-ui,sans-serif; background:#f7f8fa; color:var(--g700); }
.wrap { max-width:560px; margin:0 auto; padding:32px 16px; }
.card { background:#fff; border:1px solid var(--g200); border-radius:14px; overflow:hidden; box-shadow:0 1px 3px rgba(0,0,0,.06); }
.head { background:var(--pw900); color:#fff; padding:20px 24px; }
.head h1 { margin:6px 0 0; font-size:18px; font-weight:700; }
.head p { margin:4px 0 0; font-size:13px; color:#b8cde5; }
.logo { font-weight:700; color:#fff; font-size:15px; }
.body { padding:24px; }
.muted { color:var(--g500); font-size:13px; line-height:1.5; }
.q { font-size:15px; font-weight:600; color:var(--g900); margin:14px 0; line-height:1.45; }
.btn { display:block; width:100%; margin-top:12px; padding:14px; border:none; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; text-align:center; }
.btn-yes { background:#166534; color:#fff; } .btn-yes:hover { background:#14532d; }
.btn-no { background:#fff; color:var(--g700); border:1px solid var(--g300); } .btn-no:hover { background:var(--g100); }
.btn:disabled { opacity:.55; cursor:not-allowed; }
.note { margin-top:16px; font-size:12px; color:var(--g500); background:var(--g100); border-radius:8px; padding:10px 12px; }
.err { margin-top:12px; font-size:13px; color:#b91c1c; background:#fef2f2; border:1px solid #fecaca; border-radius:8px; padding:10px 12px; }
.ok { margin-top:12px; font-size:14px; color:#166534; background:#dcfce7; border:1px solid #bbf7d0; border-radius:8px; padding:14px; }
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<div class="head">
<div class="logo">Performance West</div>
<h1>South Carolina intrastate insurance</h1>
<p id="subtitle">Loading…</p>
</div>
<div class="body">
<div id="content">
<p class="muted">In South Carolina, a for-hire carrier registers with the SCDMV
(Certificate of Compliance). The one thing the state needs from your insurance
company is a liability filing called a <strong>Form E</strong>.</p>
<p class="q">Does your insurance company have (or can they file) a Form E showing
at least <span id="minimum">$750,000</span> in liability coverage for South
Carolina intrastate operation?</p>
<button id="yes" class="btn btn-yes">✅ Yes — my insurer can file the Form E</button>
<button id="no" class="btn btn-no">❌ No / not sure — I need the right insurance</button>
<div class="note">A Form E must be filed by your insurance <strong>company</strong>
directly with SCDMV. A regular ACORD certificate of insurance is not accepted.
If you choose "No," we'll connect you with a broker who can write SC intrastate
liability and file the Form E for you.</div>
<div id="err" class="err" style="display:none;"></div>
</div>
<div id="done" class="ok" style="display:none;"></div>
<div id="notfound" class="muted" style="display:none;">This link is invalid or the order was already handled.</div>
</div>
</div>
</div>
<script>
const API = window.__PW_API;
const $ = (id) => document.getElementById(id);
const params = new URLSearchParams(location.search);
const orderId = params.get("order") || "";
const preset = (params.get("have") || "").toLowerCase();
async function load() {
if (!orderId) { $("content").style.display = "none"; $("notfound").style.display = "block"; return; }
try {
const r = await fetch(API + "/api/v1/compliance-orders/" + encodeURIComponent(orderId));
if (!r.ok) throw new Error("nf");
const order = await r.json();
const intake = (order.intake_data && typeof order.intake_data === "object") ? order.intake_data : {};
const name = intake.entity_name || order.customer_name || "";
$("subtitle").textContent = `${name} · ${order.order_number}`;
const bracket = (intake.gross_weight_bracket || "").toLowerCase();
$("minimum").textContent = (bracket === "under_10k" || bracket === "lt_10k") ? "$300,000" : "$750,000";
} catch (e) {
// Still allow answering even if lookup fails.
$("subtitle").textContent = orderId;
}
// If the email linked directly to a yes/no, auto-submit it.
if (preset === "yes" || preset === "no") submit(preset);
}
async function submit(answer) {
$("err").style.display = "none";
$("yes").disabled = true; $("no").disabled = true;
try {
const r = await fetch(API + "/api/v1/compliance-orders/" + encodeURIComponent(orderId) + "/sc-insurance?have=" + answer, {
method: "POST", headers: { "Content-Type": "application/json" }, body: "{}",
});
const d = await r.json();
if (!r.ok) throw new Error(d.error || "Could not record your answer");
$("content").style.display = "none";
$("done").style.display = "block";
$("done").textContent = d.message || "Thank you — we've recorded your answer.";
} catch (e) {
$("err").textContent = e.message; $("err").style.display = "block";
$("yes").disabled = false; $("no").disabled = false;
}
}
$("yes").addEventListener("click", () => submit("yes"));
$("no").addEventListener("click", () => submit("no"));
load();
</script>
</body>
</html>