new-site/site/src/components/intake/steps/ClassificationWizard.astro
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

309 lines
12 KiB
Text

---
// ClassificationWizard — Guided Q&A to determine FCC Line 105 carrier category.
//
// Replaces the raw Line 105 multi-select with plain-English questions.
// Outputs the same data: line_105_primary + line_105_categories.
---
<div class="pw-step">
<h2>What type of carrier are you?</h2>
<p class="pw-help">
Answer a few questions about your services and we'll determine the correct FCC classification.
This affects your 499-A filing categories, USF obligations, and compliance requirements.
</p>
<div id="cw-questions"></div>
<div id="cw-result" style="display:none;margin-top:1.5rem">
<div style="padding:1rem;background:#f0fdf4;border:1px solid #86efac;border-radius:8px">
<p style="margin:0 0 .5rem;font-size:.85rem;font-weight:700;color:#166534">Your classification:</p>
<p id="cw-primary" style="margin:0 0 .25rem;font-size:1rem;font-weight:700;color:#111"></p>
<p id="cw-reason" style="margin:0;font-size:.8rem;color:#166534"></p>
<div id="cw-secondary" style="margin-top:.5rem;font-size:.8rem;color:#374151"></div>
</div>
<p style="font-size:.75rem;color:#94a3b8;margin-top:.5rem">
This is our recommendation based on your answers. You can change it on the next step if needed.
</p>
</div>
</div>
<style>
.pw-step h2 { margin: 0 0 0.5rem; color: #1a2744; }
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1.25rem; }
.cw-q { margin-bottom: 1.25rem; animation: fadeIn 0.2s ease-in; }
.cw-q-text { font-size: .95rem; font-weight: 600; color: #1a2744; margin-bottom: .5rem; }
.cw-q-hint { font-size: .8rem; color: #64748b; margin-bottom: .5rem; }
.cw-opts { display: flex; flex-wrap: wrap; gap: .5rem; }
.cw-opt { padding: .5rem 1rem; border: 2px solid #d1d5db; border-radius: 8px; cursor: pointer;
font-size: .85rem; font-weight: 500; color: #374151; background: #fff; transition: all .15s; }
.cw-opt:hover { border-color: #1e3a5f; background: #f0f4f8; }
.cw-opt.selected { border-color: #059669; background: #f0fdf4; color: #166534; font-weight: 700; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
</style>
<script>
// Decision tree for carrier classification
const QUESTIONS = [
{
id: "voice",
text: "Do you provide voice telephone service (VoIP, landline, or wireless)?",
hint: "This includes any service that connects calls to the public telephone network (PSTN).",
options: [
{ label: "Yes, over the internet (VoIP)", value: "voip" },
{ label: "Yes, over our own network (copper/fiber/wireless)", value: "facilities" },
{ label: "Yes, we resell another carrier's voice service", value: "reseller" },
{ label: "No, we don't provide voice service", value: "no" },
],
},
{
id: "broadband",
text: "Do you provide broadband internet access to end users?",
hint: "Cable, fiber, fixed wireless, DSL, or satellite internet.",
options: [
{ label: "Yes, over our own infrastructure", value: "facilities" },
{ label: "Yes, we resell another provider's broadband", value: "resale" },
{ label: "No", value: "no" },
],
},
{
id: "switching",
text: "Do you own or operate telephone switching equipment?",
hint: "Softswitch, SBC, Class 5 switch, or media gateway that routes calls.",
options: [
{ label: "Yes", value: "yes" },
{ label: "No, we use a platform provider (UCaaS, hosted PBX)", value: "no" },
],
showIf: (a: any) => a.voice !== "no",
},
{
id: "local_exchange",
text: "Do you provide local telephone service in a specific geographic area?",
hint: "Local dial tone, local phone numbers assigned to your customers.",
options: [
{ label: "Yes", value: "yes" },
{ label: "No", value: "no" },
],
showIf: (a: any) => a.voice === "facilities" || (a.voice === "voip" && a.switching === "yes"),
},
{
id: "long_distance",
text: "Do you provide long-distance or interstate calling?",
hint: "Toll calls between states, or international calling.",
options: [
{ label: "Yes", value: "yes" },
{ label: "No", value: "no" },
],
showIf: (a: any) => a.voice !== "no",
},
{
id: "wireless",
text: "Do you provide wireless (cellular) service?",
options: [
{ label: "Yes, we operate our own wireless network", value: "facilities" },
{ label: "Yes, as an MVNO (reselling another carrier's network)", value: "mvno" },
{ label: "No", value: "no" },
],
showIf: (a: any) => a.voice === "facilities",
},
{
id: "wholesale",
text: "Do you sell services primarily to other carriers (wholesale)?",
options: [
{ label: "Yes, mostly wholesale", value: "yes" },
{ label: "No, mostly retail (end users)", value: "no" },
{ label: "Both", value: "both" },
],
showIf: (a: any) => a.voice !== "no",
},
{
id: "toll_free",
text: "Do you provide toll-free numbers (800, 888, etc.) to customers?",
options: [
{ label: "Yes", value: "yes" },
{ label: "No", value: "no" },
],
showIf: (a: any) => a.voice !== "no",
},
];
function classify(answers: Record<string, string>): { primary: string; categories: string[]; reason: string } {
const cats: string[] = [];
let primary = "";
let reason = "";
// No voice service
if (answers.voice === "no") {
if (answers.broadband === "facilities") {
primary = "broadband_isp";
reason = "You provide broadband over your own infrastructure without voice. You are primarily an ISP.";
cats.push("broadband_isp");
} else if (answers.broadband === "resale") {
primary = "broadband_reseller";
reason = "You resell broadband without providing voice service. You may not need to file 499-A.";
cats.push("broadband_reseller");
} else {
primary = "other";
reason = "Based on your answers, you don't appear to be a telecommunications carrier. Contact us if you're unsure.";
}
return { primary, categories: cats, reason };
}
// VoIP provider
if (answers.voice === "voip") {
primary = "interconnected_voip";
reason = "You provide VoIP service connected to the PSTN. This is the most common category for modern voice carriers.";
cats.push("interconnected_voip");
if (answers.switching === "yes" && answers.local_exchange === "yes") {
cats.push("clec");
reason += " You also provide local exchange service with your own switching, making you a CLEC as well.";
}
}
// Facilities-based voice
if (answers.voice === "facilities") {
if (answers.wireless === "facilities") {
primary = "cmrs";
reason = "You operate your own wireless network. You are a Commercial Mobile Radio Service (CMRS) provider.";
cats.push("cmrs");
} else if (answers.wireless === "mvno") {
primary = "cmrs_reseller";
reason = "You resell wireless service as an MVNO.";
cats.push("cmrs_reseller");
} else if (answers.local_exchange === "yes") {
primary = "clec";
reason = "You provide local telephone service over your own facilities. You are a Competitive Local Exchange Carrier (CLEC).";
cats.push("clec");
} else {
primary = "interconnected_voip";
reason = "You provide voice service over your own facilities.";
cats.push("interconnected_voip");
}
}
// Voice reseller
if (answers.voice === "reseller") {
primary = "voip_reseller";
reason = "You resell another carrier's voice service. You are a VoIP or voice reseller.";
cats.push("voip_reseller");
}
// IXC if long distance
if (answers.long_distance === "yes" && !cats.includes("cmrs")) {
cats.push("ixc");
if (!primary || primary === "interconnected_voip") {
// Only make IXC primary if they're primarily long-distance
}
}
// Toll-free
if (answers.toll_free === "yes") {
cats.push("toll_free_provider");
}
// Broadband
if (answers.broadband === "facilities") {
cats.push("broadband_isp");
}
// Wholesale
if (answers.wholesale === "yes" || answers.wholesale === "both") {
cats.push("wholesale");
}
if (!primary) primary = cats[0] || "other";
return { primary, categories: [...new Set(cats)], reason };
}
// Render Q&A
const container = document.getElementById("cw-questions")!;
const answers: Record<string, string> = {};
let questionIndex = 0;
function renderQuestion(idx: number) {
// Find the next applicable question
while (idx < QUESTIONS.length) {
const q = QUESTIONS[idx];
if (!q.showIf || q.showIf(answers)) break;
idx++;
}
if (idx >= QUESTIONS.length) {
showResult();
return;
}
questionIndex = idx;
const q = QUESTIONS[idx];
const div = document.createElement("div");
div.className = "cw-q";
div.innerHTML = `
<p class="cw-q-text">${q.text}</p>
${q.hint ? `<p class="cw-q-hint">${q.hint}</p>` : ""}
<div class="cw-opts">
${q.options.map((o) => `<button type="button" class="cw-opt" data-value="${o.value}">${o.label}</button>`).join("")}
</div>
`;
container.appendChild(div);
div.querySelectorAll(".cw-opt").forEach((btn) => {
btn.addEventListener("click", () => {
answers[q.id] = (btn as HTMLElement).dataset.value!;
// Highlight selected
div.querySelectorAll(".cw-opt").forEach((b) => b.classList.remove("selected"));
btn.classList.add("selected");
// Next question after brief delay
setTimeout(() => renderQuestion(idx + 1), 300);
});
});
}
function showResult() {
const result = classify(answers);
document.getElementById("cw-result")!.style.display = "block";
const labels: Record<string, string> = {
interconnected_voip: "Interconnected VoIP Provider",
clec: "Competitive Local Exchange Carrier (CLEC)",
ixc: "Interexchange Carrier (IXC)",
cmrs: "Commercial Mobile Radio Service (CMRS)",
cmrs_reseller: "CMRS Reseller (MVNO)",
voip_reseller: "VoIP Reseller",
broadband_isp: "Broadband Internet Service Provider (ISP)",
broadband_reseller: "Broadband Reseller",
toll_free_provider: "Toll-Free Provider",
wholesale: "Wholesale Provider",
other: "Other / Not Classified",
};
document.getElementById("cw-primary")!.textContent = labels[result.primary] || result.primary;
document.getElementById("cw-reason")!.textContent = result.reason;
const secondary = result.categories.filter((c) => c !== result.primary);
const secEl = document.getElementById("cw-secondary")!;
if (secondary.length) {
secEl.innerHTML = "<strong>Additional categories:</strong> " + secondary.map((c) => labels[c] || c).join(", ");
}
// Save to intake data
const PW = (window as any).PWIntake;
if (PW) {
PW.patchIntakeData({
carrier_classification_answers: answers,
line_105_primary: result.primary,
line_105_categories: result.categories,
});
}
}
// Start
window.addEventListener("pw:step-shown", (evt: any) => {
if (evt.detail.step !== "classification") return;
// Reset if re-entering
container.innerHTML = "";
Object.keys(answers).forEach((k) => delete answers[k]);
document.getElementById("cw-result")!.style.display = "none";
renderQuestion(0);
});
// Also start immediately if this is the current step
renderQuestion(0);
</script>