Split retail/wholesale into per-service radios for voice and broadband

Voice and broadband can have independent customer models (e.g. wholesale
voice + retail broadband). Each service type now gets its own inline
retail/wholesale/both radio when checked. Derivation logic updated:
- Voice carriers always need RMD+CPNI regardless of mode
- BDC only required when broadband has retail end users
- CALEA still triggered by voice or facilities-based broadband

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-04-29 09:12:18 -05:00
parent b473bf1783
commit 95d4779660

View file

@ -87,9 +87,22 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
<div id="q1">
<p class="q-label">What type of service will you offer?</p>
<p class="q-hint">Select all that apply.</p>
<label class="svc-row"><input type="checkbox" data-svc="voice"> <span class="svc-name">Voice / phone service</span> <span class="svc-inc">VoIP, CLEC, or traditional</span></label>
<label class="svc-row"><input type="checkbox" data-svc="broadband"> <span class="svc-name">Broadband internet service</span> <span class="svc-inc">Retail or wholesale ISP</span></label>
<label class="svc-row"><input type="checkbox" data-svc="wholesale"> <span class="svc-name">Wholesale only</span> <span class="svc-inc">Sell to other carriers, not end users</span></label>
<div id="q1-voice-mode" class="hidden" style="margin-left:1.5rem;margin-bottom:.5rem;padding:.4rem .65rem;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
<p style="font-size:.8rem;font-weight:600;color:#6b7280;margin-bottom:.25rem">Who will you sell voice service to?</p>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;margin-bottom:.15rem;cursor:pointer"><input type="radio" name="voice_mode" value="retail" checked> Retail — end users (businesses or consumers)</label>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;margin-bottom:.15rem;cursor:pointer"><input type="radio" name="voice_mode" value="wholesale"> Wholesale — sell to other carriers or resellers</label>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;cursor:pointer"><input type="radio" name="voice_mode" value="both"> Both retail and wholesale</label>
</div>
<label class="svc-row"><input type="checkbox" data-svc="broadband"> <span class="svc-name">Broadband internet service</span> <span class="svc-inc">ISP, fiber, fixed wireless</span></label>
<div id="q1-broadband-mode" class="hidden" style="margin-left:1.5rem;margin-bottom:.5rem;padding:.4rem .65rem;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
<p style="font-size:.8rem;font-weight:600;color:#6b7280;margin-bottom:.25rem">Who will you sell broadband service to?</p>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;margin-bottom:.15rem;cursor:pointer"><input type="radio" name="broadband_mode" value="retail" checked> Retail — end users (businesses or consumers)</label>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;margin-bottom:.15rem;cursor:pointer"><input type="radio" name="broadband_mode" value="wholesale"> Wholesale — sell to other carriers or ISPs</label>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.84rem;cursor:pointer"><input type="radio" name="broadband_mode" value="both"> Both retail and wholesale</label>
</div>
</div>
<!-- Q2: Voice delivery (shown if voice selected) -->
@ -327,7 +340,8 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
// ── Wizard state ──
var wizard = {
serviceTypes: [], voiceDelivery: '', infraNeeds: [],
serviceTypes: [], voiceMode: 'retail', broadbandMode: 'retail',
voiceDelivery: '', infraNeeds: [],
broadbandType: '', operatingStates: '',
// Derived
needsRmd: false, needsCpni: false, needsCalea: false, needsBdc: false,
@ -356,11 +370,22 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
document.querySelectorAll('[data-svc]').forEach(function(cb) {
cb.addEventListener('change', function() {
wizard.serviceTypes = Array.from(document.querySelectorAll('[data-svc]:checked')).map(function(c) { return c.dataset.svc; });
document.getElementById('q2').classList.toggle('hidden', wizard.serviceTypes.indexOf('voice') < 0);
document.getElementById('q4').classList.toggle('hidden', wizard.serviceTypes.indexOf('broadband') < 0);
document.getElementById('q5').classList.remove('hidden');
var hasVoice = wizard.serviceTypes.indexOf('voice') >= 0;
var hasBroadband = wizard.serviceTypes.indexOf('broadband') >= 0;
document.getElementById('q1-voice-mode').classList.toggle('hidden', !hasVoice);
document.getElementById('q1-broadband-mode').classList.toggle('hidden', !hasBroadband);
document.getElementById('q2').classList.toggle('hidden', !hasVoice);
document.getElementById('q4').classList.toggle('hidden', !hasBroadband);
if (hasVoice || hasBroadband) document.getElementById('q5').classList.remove('hidden');
});
});
// Voice/broadband mode radios
document.querySelectorAll('input[name=voice_mode]').forEach(function(r) {
r.addEventListener('change', function() { wizard.voiceMode = this.value; });
});
document.querySelectorAll('input[name=broadband_mode]').forEach(function(r) {
r.addEventListener('change', function() { wizard.broadbandMode = this.value; });
});
// ── Q2: Voice delivery ──
document.querySelectorAll('[data-voice]').forEach(function(btn) {
@ -408,12 +433,15 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
var hasVoice = wizard.serviceTypes.indexOf('voice') >= 0;
var hasBroadband = wizard.serviceTypes.indexOf('broadband') >= 0;
var isWholesale = wizard.serviceTypes.indexOf('wholesale') >= 0;
var bbRetail = hasBroadband && (wizard.broadbandMode === 'retail' || wizard.broadbandMode === 'both');
// Voice carriers always need RMD + CPNI regardless of retail/wholesale
wizard.needsRmd = hasVoice;
wizard.needsCpni = hasVoice;
// CALEA: all voice carriers + facilities-based broadband
wizard.needsCalea = hasVoice || (hasBroadband && wizard.broadbandType === 'facilities');
wizard.needsBdc = hasBroadband && !isWholesale;
// BDC only for broadband with retail end users (not wholesale-only broadband)
wizard.needsBdc = bbRetail;
wizard.needsOcn = wizard.infraNeeds.indexOf('lcr') >= 0 || wizard.infraNeeds.indexOf('own_dids') >= 0 ||
wizard.infraNeeds.indexOf('interconnect') >= 0 || wizard.infraNeeds.indexOf('stir_sign') >= 0;
wizard.needsStirShaken = wizard.infraNeeds.indexOf('stir_sign') >= 0 || wizard.needsOcn;
@ -675,6 +703,8 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
address_zip: wizard.addrZip,
service_wizard: {
service_types: wizard.serviceTypes,
voice_mode: wizard.voiceMode,
broadband_mode: wizard.broadbandMode,
voice_delivery: wizard.voiceDelivery,
infra_needs: wizard.infraNeeds,
broadband_type: wizard.broadbandType,