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>
This commit is contained in:
justin 2026-04-27 06:54:22 -05:00
commit f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "bdc-broadband";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Availability data only (no voice subscription). For broadband-focused ISPs and fixed wireless providers.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "bdc-filing";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Both BDC blocks in one order — broadband deployment + voice subscription. For carriers with both.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "bdc-voice";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Voice subscriber counts only — the part of the legacy Form 477 that now files through BDC. For VoIP resellers + CLECs.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "calea-ssi";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "System Security and Integrity plan required of every common carrier under 47 USC § 229 / 47 CFR § 1.20003. Kept internally, provided to DOJ on subpoena.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "cdr-analysis";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Classified traffic study from your CDRs — feeds the 499-A with actual interstate/international percentages instead of the 64.9% safe harbor.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "cores-frn-registration";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Register your carrier in FCC CORES and obtain your FRN. Required before any other FCC filing.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "cpni-certification";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "47 CFR § 64.2009 annual CPNI certification filed at FCC ECFS docket 06-36. Due March 1.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
// Always hide price + tax on intake pages — if the user is here, they've
// already paid via the order page or batch checkout.
const price = document.getElementById("pw-price");
const tax = document.getElementById("pw-tax-notice");
if (price) price.style.display = "none";
if (tax) tax.style.display = "none";
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "dc-agent";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Your required D.C. registered agent for service of process (47 CFR § 413). Uses Northwest Registered Agent wholesale.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-499-initial";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "New carrier registration with USAC — obtain your Filer ID (812xxx) and unlock all future 499-A filings.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-499a-499q";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Annual 499-A plus the four quarterly 499-Q filings — one bundle, full year covered.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-499a";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Annual Telecommunications Reporting Worksheet. Due April 1 for the prior year's revenue.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-63-11-notification";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "47 CFR § 63.11 notification filed with the FCC International Bureau. Required when U.S. carriers are affiliated with a foreign carrier serving the same route.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-compliance-checkup";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Diagnostic check — CORES, RMD, STIR/SHAKEN, CPNI, 499-A status. No filing, just a report.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "fcc-full-compliance";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "RMD + CPNI + STIR/SHAKEN + 499-A + 499-Q in one order. Every annual filing, 15% bundle discount.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,70 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
// URL param ?multi=1 selects the multi-state discounted SKU.
const isMulti = Astro.url.searchParams.get("multi") === "1";
const slug = isMulti ? "foreign-qualification-multi" : "foreign-qualification-single";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = "Foreign Qualification (Certificate of Authority)";
const description =
"Register your existing LLC or corporation to do business in additional " +
"US states. Required when expanding operations, for FCC carriers serving " +
"multiple states, or for establishing nexus in a new jurisdiction.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{title}</h1>
<p class="pw-price" id="pw-price">
Service fee: {formatUSD(meta?.price_cents ?? 0)}
{isMulti ? " per state" : ""}
<span class="pw-price-note"> + state filing fees + registered agent</span>
</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
{!isMulti && (
<p class="pw-multi-cta">
Registering in multiple states?
<a href="/order/foreign-qualification?multi=1">
Switch to multi-state pricing ($99/state)
</a>
</p>
)}
{isMulti && (
<p class="pw-multi-cta">
Only need one state?
<a href="/order/foreign-qualification">
Switch to single-state pricing ($149)
</a>
</p>
)}
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "foreign_qual", "review", "payment"]} title={title} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.5rem; }
.pw-price-note { font-size: 0.9rem; font-weight: 400; color: #64748b; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
.pw-multi-cta { font-size: 0.9rem; color: #475569; margin-top: 0.5rem; }
.pw-multi-cta a { color: #2563eb; text-decoration: underline; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "new-carrier-bundle";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Start-to-finish for a brand-new VoIP carrier: FRN + 499 Initial + RMD + CPNI + CALEA SSI. Everything you need to legally start billing customers.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "ocn-registration";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Obtain an Operating Company Number from NECA. Required for STIR/SHAKEN signing authority and direct numbering access.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,46 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "rmd-filing";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Robocall Mitigation Database filing. Annual recertification required March 1 (March 2 in 2026).";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)} <span style="font-size:0.85rem;font-weight:400;color:#64748b;">+ $100 FCC filing fee</span></p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
<p style="font-size:0.82rem;color:#92400e;background:#fefce8;border-left:3px solid #f59e0b;padding:0.5rem 0.75rem;border-radius:0 4px 4px 0;margin-top:0.5rem;max-width:48rem;">
The FCC charges a $100 filing fee for RMD registrations and recertifications (effective 2025). This fee is passed through at cost and paid directly to the FCC during submission.
</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
// Always hide price + tax + FCC fee note on intake pages — if the user
// is here, they've already paid via the order page or batch checkout.
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
const f = document.querySelector(".pw-order-intro p[style*='fefce8']") as HTMLElement;
if (p) p.style.display = "none";
if (t) t.style.display = "none";
if (f) f.style.display = "none";
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,41 @@
---
import Base from "../../layouts/Base.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
const slug = "stir-shaken";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "Posture update + RMD refresh + STI-CA vendor coordination. For carriers moving from robocall-mitigation to partial implementation.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-price" id="pw-price">{formatUSD(meta?.price_cents ?? 0)}</p>
<div id="pw-tax-notice"><TaxDeductibilityNotice /></div>
<p class="pw-desc">{description}</p>
</section>
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
if (new URLSearchParams(window.location.search).has("token")) {
const p = document.getElementById("pw-price");
const t = document.getElementById("pw-tax-notice");
if (p) p.style.display = "none";
if (t) t.style.display = "none";
}
</script>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>

View file

@ -0,0 +1,309 @@
---
import Base from "../../../layouts/Base.astro";
const title = "499-A Traffic Study — Carrier CDR Data Upload & Classification";
const description = "Upload your call detail records, get a signed traffic study for your FCC Form 499-A with every jurisdiction classified — interstate, intrastate, international, indeterminate, wholesale vs. retail, and the Block 5 regional breakouts.";
---
<Base title={title} description={description}>
<main>
<div class="pw-hero">
<h1>499-A Traffic Study</h1>
<p style="font-size: 1.1rem; max-width: 42rem;">
Send us your call records. We classify every call for your FCC Form 499-A
and hand you a signed study you can file against &mdash; with a 5-year
audit trail, automatic regional breakouts, and a pre-filled 499-A worksheet.
</p>
</div>
<section id="why">
<h2>Why the numbers on your 499-A matter</h2>
<p>
USAC assesses Universal Service Fund contributions on the interstate and
international portion of your telecom revenue. Get the percentage wrong
on your 499-A and you either overpay (and over-charge your customers),
underpay (and invite an audit), or get hit with a monetary forfeiture.
For 2026, inaccurate filings can carry up to <strong>$251,322 per
violation</strong>.
</p>
<p>
Most small carriers default to the 64.9% interconnected-VoIP safe harbor
because computing the actual split is a pain: pull CDRs from the switch,
look up hundreds of area codes, allocate wholesale vs. retail, run the
regional breakouts, and keep the spreadsheet for five years in case of
audit. We do all of that for you, on autopilot, every quarter.
</p>
</section>
<section id="how-data-gets-to-us">
<h2>How you send us your data</h2>
<p>Pick whichever fits your operation &mdash; you can use more than one at the same time:</p>
<div class="pw-card">
<strong>Secure file drop (SFTP / FTPS)</strong>
<p style="margin: 0.4rem 0 0;">
Enable it in your portal and we generate a username and one-time
random password pointed at your own encrypted storage bucket. Works
with any standard SFTP or FTPS client or automation script. TLS-only.
</p>
</div>
<div class="pw-card">
<strong>We pull directly from your switch</strong>
<p style="margin: 0.4rem 0 0;">
Pick your switch from the dropdown and hand us credentials once; we
grab new CDRs on the schedule you choose. Supported platforms:
</p>
<ul style="margin-top: 0.5rem;">
<li>NetSapiens</li>
<li>Metaswitch (iCM / Provisioning Server)</li>
<li>FreeSWITCH</li>
<li>Asterisk / FreePBX / AsteriskNOW</li>
<li>Ribbon / Sonus SBC (EMA / PSX)</li>
<li>Sansay SBC (SSM)</li>
<li>Cisco BroadWorks</li>
<li>2600Hz Kazoo</li>
<li>Grandstream UCM (62xx / 63xx)</li>
<li>46Labs (Peering / NOVA)</li>
<li>SIP Navigator (Cataleya Orchid One)</li>
</ul>
<p style="margin: 0.5rem 0 0; color: var(--pw-muted); font-size: 0.9rem;">
Don&apos;t see yours? Pick <em>Other</em> and configure SFTP / FTP / FTPS /
HTTPS / S3 transport manually with any CDR format &mdash; we adapt to
whatever column layout your switch emits.
</p>
</div>
<div class="pw-card">
<strong>Drag-and-drop upload in your portal</strong>
<p style="margin: 0.4rem 0 0;">
Quickest path for one-off batches or small carriers. Files go
straight to an encrypted bucket &mdash; no email, no disks, no
PII in flight.
</p>
</div>
<div class="pw-card">
<strong>Real-time webhook</strong>
<p style="margin: 0.4rem 0 0;">
For platforms that emit per-call records as they happen, point the
stream at the webhook URL from your portal. Calls classify within
seconds of landing.
</p>
</div>
<p style="color: var(--pw-muted); font-size: 0.9rem;">
All transport is encrypted. Files are stored in an encrypted bucket
scoped to your account. Default retention is 5 years &mdash; matches
USAC&apos;s audit window per 47 CFR &sect; 54.711(a).
</p>
</section>
<section id="what-we-classify">
<h2>What we classify</h2>
<ul>
<li>
<strong>Interstate / intrastate / international</strong> &mdash; every
call, based on calling and called numbers. We use NANPA area-code
records and the FCC&apos;s country-code assignments.
</li>
<li>
<strong>Indeterminate</strong> &mdash; calls we can&apos;t resolve to
one state/country. Kept visible so you can see what&apos;s unclear
rather than silently miscounting. Common causes: anonymized caller,
unrouteable DID, switch-side caller-ID strip.
</li>
<li>
<strong>Wholesale vs. retail</strong> &mdash; fed by a one-time
mapping of your trunk groups and customer account IDs in the portal,
or by a per-row tag column if your CDR carries one.
</li>
<li>
<strong>Block 5 regional breakouts</strong> (Lines 503&ndash;510) &mdash;
produced <em>two ways</em>, by the caller&apos;s originating state
<em>and</em> by your customer&apos;s billing-address state. You (or we)
pick whichever the FCC has asked for at filing time.
</li>
</ul>
</section>
<section id="what-you-get">
<h2>What you get back</h2>
<ul>
<li>A <strong>signed traffic study</strong> (PDF + XLSX) with jurisdictional percentages, both regional breakouts, a methodology statement, sample size, and revenue-vs-minutes cross-check</li>
<li>A <strong>pre-filled 499-A workbook</strong> &mdash; Block 3, Block 4-A, and Block 5 cells populated from your actual data</li>
<li>A <strong>data-quality report</strong> listing anything we couldn&apos;t classify so you can fix switch issues before next quarter</li>
<li>A <strong>5-year retention guarantee</strong> on the underlying CDRs &mdash; ready for a USAC audit on 24 hours&apos; notice</li>
</ul>
</section>
<section id="revenue-first">
<h2>We bill USF by actual revenue when we can</h2>
<p>
USAC assesses on <strong>revenue</strong>, not minutes. If your CDRs carry
a per-call charge (most commercial switches do &mdash; Asterisk nibblebill,
NetSapiens <code>cost</code>, Ribbon <code>totalCharge</code>, etc.), we
compute your percentages from actual dollars. Minutes-weighted numbers
appear beside the revenue view as a sanity check.
</p>
<p>
Flat-rate line service (monthly seat pricing with no per-call charge) is
the one case where minutes-only estimation is the correct choice. That&apos;s
an explicit opt-in on your profile &mdash; we label the study accordingly
so the methodology section is defensible under audit.
</p>
</section>
<section id="benefits-vs-manual">
<h2>vs. doing it yourself</h2>
<table class="pw-pricing">
<thead>
<tr><th>Doing it yourself</th><th>With us</th></tr>
</thead>
<tbody>
<tr><td>Pull raw CDRs from the switch, wrestle with format changes</td><td>Automated the moment your switch drops a new file</td></tr>
<tr><td>Look up hundreds of area codes by hand, maintain a spreadsheet</td><td>Full NANPA lookup, updated continuously</td></tr>
<tr><td>Eyeball &ldquo;is this interstate?&rdquo; from phone numbers</td><td>Deterministic rules, same answer every quarter</td></tr>
<tr><td>Guess at the wholesale/retail split</td><td>Per-call bucketed from trunk-group mapping you set once</td></tr>
<tr><td>Use the 64.9% safe harbor even when your actual % is lower</td><td>Use your actual study; lower USF pass-through when math justifies it</td></tr>
<tr><td>Weight by minutes because it&apos;s easier</td><td>Weight by actual per-call revenue &mdash; the denominator USAC uses</td></tr>
<tr><td>No audit trail when USAC asks</td><td>Signed study with methodology + sample size, delivered quarterly</td></tr>
<tr><td>One-off Excel with formulas you&apos;ll forget in 12 months</td><td>Archived, dated, downloadable from your portal</td></tr>
</tbody>
</table>
</section>
<section id="mistakes">
<h2>Common mistakes we prevent</h2>
<ul>
<li><strong>Counting failed/abandoned calls as billable minutes</strong> &mdash; inflates the denominator; USAC fines follow</li>
<li><strong>Attestation confusion</strong> &mdash; treating all outbound as interstate when the answering point was actually intrastate</li>
<li><strong>Toll-free inversion</strong> &mdash; 8XX calls are paid by the called party; Block 5 attribution flips. Customers routinely put these in the wrong region.</li>
<li><strong>Time-zone drift</strong> &mdash; naive UTC-vs-local splits move calls across midnight boundaries and miscount a day&apos;s minutes</li>
<li><strong>Indeterminate calls hidden in &ldquo;interstate&rdquo;</strong> &mdash; bumps your USF contribution for minutes you shouldn&apos;t be paying on</li>
<li><strong>Stale trunk-group tags</strong> &mdash; a customer moves from wholesale to retail and nobody updates the spreadsheet; we re-prompt quarterly</li>
<li><strong>Safe harbor when actual interstate % is lower</strong> &mdash; overpaying into USF and over-charging end users</li>
<li><strong>Minutes-weighting a per-call carrier</strong> &mdash; a few long-international calls can swing a minutes ratio sharply away from the real revenue mix</li>
</ul>
</section>
<section id="cdr-fields">
<h2>What CDR fields we need</h2>
<p>No custom development on your side. If your CDR carries these fields, you&apos;re ready:</p>
<h3>Required &mdash; the system won&apos;t classify without these</h3>
<table>
<thead>
<tr><th>Field</th><th>Typical CSV header</th><th>Why we need it</th></tr>
</thead>
<tbody>
<tr><td>Call start time</td><td><code>start_time</code>, <code>calldate</code>, <code>start_stamp</code>, <code>initiated</code></td><td>Assigns the call to a reporting period and quarter</td></tr>
<tr><td>Calling number (ANI)</td><td><code>src</code>, <code>caller_id_number</code>, <code>orig_number</code>, <code>ani</code></td><td>Derives originating state via NPA</td></tr>
<tr><td>Called number (DNIS)</td><td><code>dst</code>, <code>destination_number</code>, <code>term_number</code>, <code>dnis</code></td><td>Derives terminating state</td></tr>
<tr><td>Call duration</td><td><code>duration</code>, <code>billsec</code>, <code>duration_ms</code>, <code>talk_time</code></td><td>Minutes-based weighting</td></tr>
</tbody>
</table>
<h3>Strongly preferred &mdash; makes the study revenue-accurate instead of minutes-estimated</h3>
<table>
<thead>
<tr><th>Field</th><th>Typical CSV header</th><th>What it enables</th></tr>
</thead>
<tbody>
<tr>
<td>Per-call billed amount</td>
<td><code>charge</code>, <code>cost</code>, <code>amount_billed</code>, <code>rate_charge</code>, <code>nibble_total_billed</code></td>
<td>
<strong>The primary revenue signal for the traffic study.</strong>
When present, we attribute USF by actual per-call revenue &mdash;
the denominator USAC actually uses. Without it, we fall back to
minutes-only estimation, which must be opted in on your profile.
</td>
</tr>
</tbody>
</table>
<h3>Helpful &mdash; quality of attribution and audit strength improves when present</h3>
<table>
<thead>
<tr><th>Field</th><th>Typical CSV header</th><th>What it enables</th></tr>
</thead>
<tbody>
<tr><td>Call direction</td><td><code>direction</code>, <code>call_direction</code>, <code>leg_type</code></td><td>Distinguishes outbound from inbound when ANI alone can&apos;t (toll-free inversion, etc.)</td></tr>
<tr><td>Disposition / answer indicator</td><td><code>disposition</code>, <code>hangup_cause</code>, <code>answered</code></td><td>Drops failed/abandoned calls so they don&apos;t skew minutes</td></tr>
<tr><td>Trunk group ID</td><td><code>trunk_group</code>, <code>trunk</code>, <code>carrier_id</code>, <code>channel_name</code> prefix</td><td>Primary key for wholesale/retail bucketing via the portal mapping</td></tr>
<tr><td>Customer / account ID</td><td><code>account_code</code>, <code>accountcode</code>, <code>customer_id</code>, <code>sip_account</code></td><td>Secondary bucketing key when trunk group isn&apos;t granular enough</td></tr>
<tr><td>Answer time + release time</td><td><code>answer_time</code>, <code>end_stamp</code>, <code>release_time</code></td><td>Cross-checks duration, detects zero-duration anomalies</td></tr>
<tr><td>Orig / term country</td><td><code>orig_country</code>, <code>term_country</code>, <code>country_code</code></td><td>Classifies international calls with ambiguous NANP derivation (Caribbean jurisdictions etc.)</td></tr>
<tr><td>Customer-type tag (if your switch can emit it)</td><td><code>customer_type</code>, <code>market_segment</code></td><td>Optional per-row override &mdash; wins over portal mapping</td></tr>
<tr><td>Rate center / LATA</td><td><code>rate_center</code>, <code>orig_lata</code>, <code>term_lata</code></td><td>Enables local-vs-intrastate-toll split (not required for 499-A)</td></tr>
</tbody>
</table>
<div class="pw-callout">
<strong>Bottom line:</strong> if your CDRs carry start time, ANI, DNIS,
and duration, you&apos;re ready. A per-call charge or cost column turns
the study into a revenue-weighted report, which is what USAC uses. Flat-rate
line services opt into minutes-only estimation &mdash; that&apos;s standard
for retail VoIP, and we note the methodology on every study.
</div>
</section>
<section id="pricing">
<h2>Storage, processing, and retention</h2>
<p>
Your 499-A filing service covers <strong>10 GB of compressed CDR
storage</strong> and <strong>10 million classified calls per reporting
year</strong>, with <strong>5-year retention</strong> &mdash; plenty for
most small-to-midsize carriers. Larger carriers pick the smallest tier
that covers their footprint:
</p>
<table class="pw-pricing">
<thead>
<tr><th>Plan</th><th>Storage</th><th>Calls / year</th><th>Annual</th></tr>
</thead>
<tbody>
<tr><td>Included with filing</td><td>10 GB</td><td>10 M</td><td>&mdash;</td></tr>
<tr><td>Storage Tier 1</td><td>50 GB</td><td>50 M</td><td>$99</td></tr>
<tr><td>Storage Tier 2</td><td>250 GB</td><td>250 M</td><td>$299</td></tr>
<tr><td>Storage Tier 3</td><td>1 TB</td><td>1 B</td><td>$799</td></tr>
<tr><td>Enterprise</td><td>Custom</td><td>Custom</td><td>Let&apos;s talk</td></tr>
</tbody>
</table>
<p>
Your portal shows a live usage meter. At 80% we email a heads-up; at
100% you pick (at setup) whether to keep ingesting and get the next
tier billed automatically, pause new uploads until you upgrade, or
notify-only. Prior years stay retained while you&apos;re on an active
plan; if you lapse, we send a 30-day notice before any deletion.
</p>
</section>
<section id="how-it-works-across-the-year">
<h2>How it works across a filing year</h2>
<p>
Data collection runs continuously &mdash; you can send us CDRs the day you
sign up and every quarter rolls into the year&apos;s dataset
automatically. The <strong>classified study for the current reporting
year</strong> becomes available once you&apos;ve signed up for that
year&apos;s 499-A filing service (or the standalone traffic study).
Prior years you&apos;ve filed with us stay open in your portal forever.
</p>
<p>
Until the current year is paid, you&apos;ll still see ingestion health
in the portal &mdash; upload counts, data-quality warnings, quarantine
reviews &mdash; you just won&apos;t see the final percentages or download
the signed study until the filing service is paid for. This is how we
keep the service sustainable without hourly billing.
</p>
</section>
<section id="cta">
<div class="pw-callout" style="border-color: var(--pw-green); background: #ecfdf5; text-align: center; padding: 1.5rem;">
<h3 style="margin: 0 0 0.5rem; color: var(--pw-navy);">Ready to stop using the safe harbor?</h3>
<p style="margin: 0 0 1rem;">Set up a profile, send us a sample CDR, and see your actual interstate percentage &mdash; usually in under an hour.</p>
<a class="pw-cta" href="/order/fcc-499a">Start your 2026 499-A filing →</a>
</div>
</section>
</main>
</Base>

View file

@ -0,0 +1,128 @@
---
import Base from "../../../layouts/Base.astro";
const title = "FCC Form 477 Filing — What Replaced It and What You Need Now";
const description = "The FCC retired Form 477 in December 2022. Voice subscription data now files through BDC. Here's what you actually need for 2026 and how we file it for you.";
---
<Base title={title} description={description}>
<main>
<section class="pw-hero">
<h1>FCC Form 477 Filing — Where Things Stand in 2026</h1>
<p style="font-size: 1.1rem; max-width: 44rem;">
The Form 477 you remember doesn&apos;t exist anymore. But the
<em>filing obligation</em> still does. Here&apos;s what changed and
what you actually need to file.
</p>
</section>
<section>
<h2>What happened to Form 477</h2>
<p>
The FCC retired the standalone Form 477 filing system on
<strong>December 31, 2022</strong>. All voice subscription data and
all broadband deployment data moved into the <strong>Broadband Data
Collection (BDC) system</strong>. Same deadlines (March 1 for
December-31 snapshots, September 1 for June-30 snapshots), same
required data points, different portal.
</p>
<div class="pw-callout">
<strong>Key point:</strong> if someone tells you to file Form 477
in 2026, they mean <em>file your voice-subscription data through BDC</em>.
The old apps.fcc.gov/form477 login page will redirect you to
bdc.fcc.gov — that&apos;s on purpose.
</div>
</section>
<section>
<h2>Who still has to file</h2>
<ul>
<li><strong>Facilities-based providers of fixed or mobile voice service</strong> — interconnected VoIP providers, CLECs, and wireline ILECs. File voice subscription counts.</li>
<li><strong>Facilities-based broadband ISPs</strong> — file availability data (by serviceable location) and subscription counts.</li>
<li><strong>Interconnected VoIP-only resellers</strong> — still required to file voice subscriber counts even though you don&apos;t own the underlying infrastructure.</li>
</ul>
</section>
<section>
<h2>Two ways we can file for you</h2>
<div class="pw-card-grid">
<a class="pw-card" href="/order/bdc-voice">
<h3>BDC Voice Subscription only</h3>
<p class="pw-card-price">$149 per filing</p>
<p>
The voice part of what used to be Form 477. Tell us your voice
subscriber count at the snapshot date; we file it.
</p>
<p class="pw-card-hint">
<em>Best for</em>: VoIP resellers, CLECs, competitive carriers
that don&apos;t offer broadband.
</p>
</a>
<a class="pw-card" href="/order/bdc-broadband">
<h3>BDC Broadband Deployment only</h3>
<p class="pw-card-price">$199 per filing</p>
<p>
Availability data — which locations you serve and at what speeds.
Upload your availability CSV; we validate and submit.
</p>
<p class="pw-card-hint">
<em>Best for</em>: ISPs, fixed wireless providers, broadband-only
companies.
</p>
</a>
<a class="pw-card" href="/order/bdc-filing">
<h3>Both — BDC Voice + Broadband</h3>
<p class="pw-card-price">$299 per filing</p>
<p>
One filing covers both blocks. Saves $49 vs. buying them
separately.
</p>
<p class="pw-card-hint">
<em>Best for</em>: full-service carriers offering both voice
and broadband.
</p>
</a>
</div>
</section>
<section>
<h2>Why you don&apos;t want to file this yourself</h2>
<ul>
<li><strong>BDC portal authentication</strong> — requires an authorized CORES session tied to your FRN; miss a step and you get locked out of your own filing.</li>
<li><strong>Availability CSV format is strict</strong> — the BDC system validates every row; a misformatted header or bad location ID rejects the entire upload.</li>
<li><strong>Missed filings</strong> — the FCC has begun issuing forfeitures to providers who miss BDC deadlines. Don&apos;t be the cautionary tale.</li>
<li><strong>Voice-only confusion</strong> — many voice-only VoIP resellers skip BDC because &quot;it&apos;s broadband data.&quot; Wrong. The voice subscription block is mandatory and lives in BDC now.</li>
</ul>
</section>
<section>
<h2>Deadlines for 2026</h2>
<table>
<thead><tr><th>Snapshot date</th><th>Filing deadline</th><th>What to file</th></tr></thead>
<tbody>
<tr><td>December 31, 2025</td><td>March 1, 2026</td><td>Voice subscribers + broadband availability + subscriber counts as of Dec 31, 2025</td></tr>
<tr><td>June 30, 2026</td><td>September 1, 2026</td><td>Voice subscribers + broadband availability + subscriber counts as of Jun 30, 2026</td></tr>
</tbody>
</table>
</section>
<section class="pw-cta-section">
<a class="pw-cta" href="/order/bdc-voice">File your BDC voice data (formerly Form 477) →</a>
</section>
</main>
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-hero { padding: 2.5rem 1.5rem; background: linear-gradient(135deg, #1a2744 0%, #2d4e78 100%); color: #fff; border-radius: 14px; margin-bottom: 2rem; }
.pw-hero h1 { color: #fff; margin: 0 0 0.5rem; }
.pw-callout { border-left: 4px solid #2d4e78; background: #f0f9ff; padding: 0.9rem 1.1rem; border-radius: 0 6px 6px 0; margin: 1rem 0; }
.pw-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin: 1rem 0; }
.pw-card { display: block; padding: 1.25rem; border: 1px solid #e2e8f0; border-radius: 10px; text-decoration: none; color: inherit; transition: box-shadow 0.15s; }
.pw-card:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); border-color: #2d4e78; }
.pw-card h3 { margin: 0 0 0.3rem; color: #1a2744; font-size: 1.05rem; }
.pw-card-price { font-weight: 700; color: #059669; margin: 0 0 0.5rem; font-size: 1rem; }
.pw-card-hint { color: #64748b; font-size: 0.85rem; margin-top: 0.5rem; }
.pw-cta-section { margin: 3rem 0; text-align: center; }
.pw-cta { display: inline-block; padding: 1rem 2rem; background: #059669; color: #fff; text-decoration: none; font-weight: 600; border-radius: 8px; font-size: 1.05rem; }
.pw-cta:hover { background: #047857; }
</style>

View file

@ -0,0 +1,622 @@
---
import Base from "../../layouts/Base.astro";
---
<Base
title="FCC Compliance Check — Free Telecom Filing Status"
description="Enter your FCC Registration Number (FRN) to check your RMD, CPNI, 499-A, and STIR/SHAKEN compliance status instantly. Free tool from Performance West."
>
<main class="max-w-3xl mx-auto px-4 py-10">
<!-- Breadcrumb -->
<nav class="text-sm text-gray-500 mb-6" aria-label="Breadcrumb">
<a href="/" class="hover:text-pw-600">Home</a>
<span class="mx-1">/</span>
<a href="/tools" class="hover:text-pw-600">Free Tools</a>
<span class="mx-1">/</span>
<span class="text-gray-800 font-medium">FCC Compliance Check</span>
</nav>
<!-- Header -->
<h1 class="text-3xl font-bold text-gray-900 mb-2">FCC Compliance Check</h1>
<p class="text-gray-600 mb-6">
Enter a business name or FRN below to instantly check FCC filing and compliance status.
</p>
<!-- Disclaimer -->
<div class="bg-gray-100 border border-gray-200 rounded-lg p-4 text-sm text-gray-600 mb-8">
This tool queries publicly available FCC data and is for informational purposes only.
This is not legal or compliance advice.
</div>
<!-- Search Box -->
<div class="bg-white border border-gray-200 rounded-xl p-6 shadow-sm mb-8">
<!-- Business Name Search -->
<label for="name-input" class="block text-sm font-medium text-gray-700 mb-1">Business Name</label>
<div class="flex gap-2 mb-2">
<input
type="text"
id="name-input"
placeholder="e.g. Verizon"
class="flex-1 border border-gray-300 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500"
/>
<button
id="name-search-btn"
class="bg-pw-600 hover:bg-pw-700 text-white font-semibold px-5 py-2 rounded-lg text-sm transition"
>
Search
</button>
</div>
<div id="name-results" class="hidden mt-3"></div>
<!-- Divider -->
<div class="flex items-center gap-3 my-5">
<div class="flex-1 border-t border-gray-200"></div>
<span class="text-sm text-gray-400">or enter FRN directly</span>
<div class="flex-1 border-t border-gray-200"></div>
</div>
<!-- FRN Search -->
<label for="frn-input" class="block text-sm font-medium text-gray-700 mb-1">FCC Registration Number (FRN)</label>
<div class="flex gap-2 mb-2">
<input
type="text"
id="frn-input"
placeholder="0012345678"
maxlength="10"
class="flex-1 border border-gray-300 rounded-lg px-4 py-2 font-mono text-lg focus:outline-none focus:ring-2 focus:ring-pw-500"
/>
<button
id="frn-check-btn"
class="bg-gray-700 hover:bg-gray-800 text-white font-semibold px-5 py-2 rounded-lg text-sm transition"
>
Check Compliance
</button>
</div>
<p class="text-xs text-gray-400">
Don't know your FRN?
<a href="https://apps.fcc.gov/cores/userLogin.do" target="_blank" rel="noopener" class="text-pw-600 hover:underline">Look it up on FCC CORES</a>
</p>
</div>
<!-- Loading -->
<div id="loading" class="hidden text-center py-10">
<svg class="animate-spin h-8 w-8 text-pw-600 mx-auto mb-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
<p class="text-gray-500 text-sm">Checking FCC databases&hellip;</p>
</div>
<!-- Error -->
<div id="error-box" class="hidden bg-red-50 border border-red-200 rounded-lg p-4 text-red-700 text-sm mb-6">
<p id="error-message"></p>
</div>
<!-- Results -->
<div id="results" class="hidden space-y-6">
<!-- Entity Header -->
<div class="bg-white border border-gray-200 rounded-xl p-6 shadow-sm">
<h2 id="entity-name" class="text-xl font-bold text-gray-900"></h2>
<p id="entity-dba" class="text-sm text-gray-500 hidden"></p>
<p id="entity-address" class="text-sm text-gray-500 mt-1"></p>
<p class="mt-2 text-sm text-gray-600">
FRN: <span id="entity-frn" class="font-mono font-semibold text-gray-800"></span>
</p>
<div id="entity-details" class="hidden mt-3 space-y-1 text-sm text-gray-600">
<p id="detail-filer-id"></p>
<p id="detail-comm-type"></p>
<p id="detail-rmd-number"></p>
<div id="detail-provider-types" class="flex flex-wrap gap-1 mt-1"></div>
<p id="detail-usf-contributor"></p>
<p id="detail-other-frns"></p>
<p id="detail-previous-names"></p>
</div>
</div>
<!-- Checks -->
<div id="checks-container" class="space-y-4"></div>
<!-- Prior-year catch-up -->
<div id="prior-year" class="hidden bg-red-50 border border-red-200 rounded-xl p-5 text-sm text-red-800">
<p class="font-semibold mb-1">Prior-Year Catch-Up May Be Required</p>
<p>One or more filings appear overdue. In some cases prior-year filings must be completed before the current year can be filed. Contact us for a full assessment.</p>
</div>
<!-- CTA Section -->
<div id="cta-section" class="bg-white border border-gray-200 rounded-xl p-6 shadow-sm">
<div id="cta-content"></div>
<!-- Payment Icons -->
<div id="payment-icons" class="hidden mt-5 flex flex-wrap items-center gap-3 justify-center">
<img src="/images/cards/visa.png" alt="Visa" class="h-7 opacity-70" />
<img src="/images/cards/mastercard.svg" alt="Mastercard" class="h-7 opacity-70" />
<img src="/images/cards/amex.png" alt="Amex" class="h-7 opacity-70" />
<img src="/images/cards/discover.svg" alt="Discover" class="h-7 opacity-70" />
<img src="/images/cards/paypal.svg" alt="PayPal" class="h-6 opacity-70" />
<span class="text-gray-300 mx-1">|</span>
<img src="/images/cards/bitcoin.svg" alt="Bitcoin" class="h-6 opacity-60" />
<img src="/images/cards/ethereum.svg" alt="Ethereum" class="h-6 opacity-60" />
<img src="/images/cards/usdc.svg" alt="USDC" class="h-6 opacity-60" />
<img src="/images/cards/usdt.svg" alt="USDT" class="h-6 opacity-60" />
<img src="/images/cards/litecoin.svg" alt="Litecoin" class="h-6 opacity-60" />
</div>
<!-- Reminder CTA (shown when all green) -->
<div id="reminder-cta" class="hidden mt-5 text-center">
<p class="text-sm text-gray-600 mb-3">Want a reminder before your next filing deadline?</p>
<button
id="subscribe-btn-compliance"
class="bg-pw-600 hover:bg-pw-700 text-white font-semibold px-6 py-2 rounded-lg text-sm transition"
>
Join Reminder List
</button>
</div>
<!-- Bottom row -->
<div class="mt-6 flex flex-wrap gap-3 justify-center text-sm">
<a href="/order/new-carrier" class="inline-flex items-center gap-1.5 bg-pw-600 hover:bg-pw-700 text-white font-semibold px-4 py-2 rounded-lg transition">
<img src="/images/flags/usa.png" alt="" class="h-4" />
Start a New US Carrier
</a>
<a href="/order/new-carrier-canada" class="inline-flex items-center gap-1.5 bg-red-600 hover:bg-red-700 text-white font-semibold px-4 py-2 rounded-lg transition">
<img src="/images/flags/canada.png" alt="" class="h-4" />
Start a New Canadian Carrier
</a>
<a href="/services" class="inline-flex items-center gap-1.5 border border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold px-4 py-2 rounded-lg transition">
View All Telecom Services
</a>
</div>
</div>
<!-- Timestamp -->
<p id="checked-at" class="text-xs text-gray-400 text-center"></p>
</div>
</main>
<script type="module" is:inline>
const API = window.__PW_API || "https://api.performancewest.net";
const nameInput = document.getElementById("name-input");
const nameSearchBtn = document.getElementById("name-search-btn");
const nameResults = document.getElementById("name-results");
const frnInput = document.getElementById("frn-input");
const frnCheckBtn = document.getElementById("frn-check-btn");
const loadingEl = document.getElementById("loading");
const errorBox = document.getElementById("error-box");
const errorMessage = document.getElementById("error-message");
const resultsEl = document.getElementById("results");
// --- Name search ---
nameSearchBtn.addEventListener("click", runNameSearch);
nameInput.addEventListener("keydown", (e) => { if (e.key === "Enter") runNameSearch(); });
async function runNameSearch() {
const name = nameInput.value.trim();
if (!name) return;
nameResults.classList.remove("hidden");
nameResults.innerHTML = '<p class="text-sm text-gray-500">Searching&hellip;</p>';
try {
const res = await fetch(`${API}/api/v1/fcc/cores-search?name=${encodeURIComponent(name)}`);
if (!res.ok) throw new Error("Search failed");
const data = await res.json();
const items = data.results || data;
if (!items || items.length === 0) {
nameResults.innerHTML = '<p class="text-sm text-gray-500">No results found.</p>';
return;
}
let html = `<p class="text-xs text-gray-400 mb-2">${items.length} result${items.length !== 1 ? "s" : ""}</p>`;
html += '<div class="space-y-1 max-h-60 overflow-y-auto">';
for (const item of items) {
const label = [
item.legal_name || "",
item.dba ? `DBA: ${item.dba}` : "",
item.frn ? `FRN: ${item.frn}` : "",
item.filer_id ? `Filer ID: ${item.filer_id}` : "",
].filter(Boolean).join(" — ");
html += `<button
data-frn="${item.frn || ""}"
class="name-result-btn block w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-pw-50 border border-transparent hover:border-pw-200 transition"
>${label}</button>`;
}
html += "</div>";
nameResults.innerHTML = html;
nameResults.querySelectorAll(".name-result-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const frn = btn.getAttribute("data-frn");
if (!frn) {
showError("No FRN available for this entity.");
return;
}
frnInput.value = frn;
runCheck();
});
});
} catch (err) {
nameResults.innerHTML = `<p class="text-sm text-red-600">Search error: ${err.message}</p>`;
}
}
// --- FRN compliance check ---
frnCheckBtn.addEventListener("click", runCheck);
frnInput.addEventListener("keydown", (e) => { if (e.key === "Enter") runCheck(); });
async function runCheck() {
const frn = frnInput.value.trim();
if (!frn) return;
// Update URL
const url = new URL(window.location);
url.searchParams.set("frn", frn);
history.replaceState(null, "", url);
// Reset UI
resultsEl.classList.add("hidden");
errorBox.classList.add("hidden");
loadingEl.classList.remove("hidden");
try {
const res = await fetch(`${API}/api/v1/fcc/lookup?frn=${frn}`);
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || body.error || `Lookup failed (${res.status})`);
}
const data = await res.json();
renderResults(data);
} catch (err) {
showError(err.message);
} finally {
loadingEl.classList.add("hidden");
}
}
function showError(msg) {
errorMessage.textContent = msg;
errorBox.classList.remove("hidden");
}
// --- Render results ---
let lastData = null;
function renderResults(data) {
lastData = data;
errorBox.classList.add("hidden");
// Entity name
document.getElementById("entity-name").textContent = data.entity_name || data.legal_name || "Unknown Entity";
document.getElementById("entity-frn").textContent = data.frn || frnInput.value.trim();
// DBA
const dbaEl = document.getElementById("entity-dba");
const dbaValues = [
data.filer_499?.trade_name,
...(data.rmd?.other_dba_names || []),
].filter((v) => v && v !== "None" && v.trim() !== "");
if (dbaValues.length > 0) {
dbaEl.textContent = "DBA: " + [...new Set(dbaValues)].join(", ");
dbaEl.classList.remove("hidden");
} else {
dbaEl.classList.add("hidden");
}
// Address
const addrEl = document.getElementById("entity-address");
const coresAddr = data.cores_address || data.address;
if (coresAddr) {
addrEl.textContent = coresAddr;
} else if (data.rmd?.business_address) {
addrEl.textContent = data.rmd.business_address;
} else if (data.filer_499?.state) {
addrEl.textContent = data.filer_499.state;
} else {
addrEl.textContent = "";
}
// Details
const detailsEl = document.getElementById("entity-details");
let hasDetails = false;
function setDetail(id, label, value) {
const el = document.getElementById(id);
if (value && value !== "None" && String(value).trim() !== "") {
el.innerHTML = `<span class="font-medium text-gray-700">${label}:</span> ${value}`;
el.classList.remove("hidden");
hasDetails = true;
} else {
el.classList.add("hidden");
}
}
setDetail("detail-filer-id", "Filer ID", data.filer_499?.filer_id || data.filer_id);
setDetail("detail-comm-type", "Comm Type", data.filer_499?.comm_type || data.comm_type);
setDetail("detail-rmd-number", "RMD Number", data.rmd?.rmd_number);
// USF Contributor
const usfRaw = data.filer_499?.usf_contributor ?? data.usf_contributor ?? null;
const usfDisplay = usfRaw === true ? "Yes" : usfRaw === false ? "No" : null;
setDetail("detail-usf-contributor", "USF Contributor", usfDisplay);
// Provider types
const ptContainer = document.getElementById("detail-provider-types");
const providerTypes = data.rmd?.provider_types || [];
if (providerTypes.length > 0) {
ptContainer.innerHTML = '<span class="font-medium text-gray-700 mr-1">Provider Types:</span>' +
providerTypes.map((t) => `<span class="inline-block bg-pw-100 text-pw-800 text-xs font-medium px-2 py-0.5 rounded-full">${t}</span>`).join("");
ptContainer.classList.remove("hidden");
hasDetails = true;
} else {
ptContainer.classList.add("hidden");
}
setDetail("detail-other-frns", "Other FRNs", data.other_frns?.join(", "));
setDetail("detail-previous-names", "Previous Names", data.previous_names?.join(", "));
if (hasDetails) {
detailsEl.classList.remove("hidden");
} else {
detailsEl.classList.add("hidden");
}
// Checks
const checksContainer = document.getElementById("checks-container");
checksContainer.innerHTML = "";
const checks = data.checks || [];
const colorMap = {
green: { bg: "bg-green-50", border: "border-green-200", iconColor: "text-green-600", textColor: "text-green-800" },
yellow: { bg: "bg-amber-50", border: "border-amber-200", iconColor: "text-amber-600", textColor: "text-amber-800" },
red: { bg: "bg-red-50", border: "border-red-200", iconColor: "text-red-600", textColor: "text-red-800" },
unknown: { bg: "bg-gray-50", border: "border-gray-200", iconColor: "text-gray-500", textColor: "text-gray-700" },
};
const icons = {
green: `<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>`,
yellow: `<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01M10.29 3.86l-8.8 15.32A1 1 0 002.36 21h19.28a1 1 0 00.87-1.5l-9.64-16.64a1 1 0 00-1.74 0z"/></svg>`,
red: `<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>`,
unknown: `<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01"/></svg>`,
};
let hasRed = false;
checks.forEach((check) => {
const status = check.status || "unknown";
if (status === "red") hasRed = true;
const c = colorMap[status] || colorMap.unknown;
const icon = icons[status] || icons.unknown;
const card = document.createElement("div");
card.className = `${c.bg} ${c.border} border rounded-xl p-4 flex items-start gap-3`;
card.setAttribute("data-check-id", check.id);
let inner = `<div class="${c.iconColor} mt-0.5 flex-shrink-0">${icon}</div>`;
inner += `<div class="flex-1">`;
inner += `<p class="font-semibold ${c.textColor}">${check.label || check.id}</p>`;
if (check.due_date) inner += `<p class="text-xs ${c.textColor} opacity-75 mt-0.5">Due: ${check.due_date}</p>`;
if (check.detail) inner += `<p class="text-sm ${c.textColor} mt-1">${check.detail}</p>`;
// BDC special
if (check.id === "bdc_filing" && status === "unknown") {
inner += `<div class="mt-3">
<p class="text-sm font-medium ${c.textColor} mb-2">Do you provide broadband internet access?</p>
<div class="flex gap-2">
<button class="bdc-yes bg-amber-500 hover:bg-amber-600 text-white text-sm font-semibold px-4 py-1.5 rounded-lg transition">Yes</button>
<button class="bdc-no bg-green-600 hover:bg-green-700 text-white text-sm font-semibold px-4 py-1.5 rounded-lg transition">No</button>
</div>
</div>`;
}
inner += `</div>`;
card.innerHTML = inner;
// BDC button handlers
if (check.id === "bdc_filing" && status === "unknown") {
card.querySelector(".bdc-yes")?.addEventListener("click", () => {
check.status = "yellow";
const cy = colorMap.yellow;
card.className = `${cy.bg} ${cy.border} border rounded-xl p-4 flex items-start gap-3`;
card.innerHTML = `<div class="${cy.iconColor} mt-0.5 flex-shrink-0">${icons.yellow}</div>
<div class="flex-1">
<p class="font-semibold ${cy.textColor}">${check.label || check.id}</p>
${check.due_date ? `<p class="text-xs ${cy.textColor} opacity-75 mt-0.5">Due: ${check.due_date}</p>` : ""}
${check.detail ? `<p class="text-sm ${cy.textColor} mt-1">${check.detail}</p>` : ""}
</div>`;
renderCta(lastData);
});
card.querySelector(".bdc-no")?.addEventListener("click", () => {
check.status = "green";
const cg = colorMap.green;
card.className = `${cg.bg} ${cg.border} border rounded-xl p-4 flex items-start gap-3`;
card.innerHTML = `<div class="${cg.iconColor} mt-0.5 flex-shrink-0">${icons.green}</div>
<div class="flex-1">
<p class="font-semibold ${cg.textColor}">${check.label || check.id}</p>
</div>`;
});
}
checksContainer.appendChild(card);
});
// Prior-year
document.getElementById("prior-year").classList.toggle("hidden", !hasRed);
// CTA
renderCta(data);
// Timestamp
document.getElementById("checked-at").textContent = "Checked at " + new Date().toLocaleString();
// Show results
resultsEl.classList.remove("hidden");
}
// --- Render CTA (separate function) ---
function renderCta(data) {
const checks = data.checks || [];
const frn = data.frn || frnInput.value.trim();
const services = [];
for (const check of checks) {
const s = check.status || "unknown";
if (s === "green") continue;
switch (check.id) {
case "rmd_filing":
services.push({ id: "rmd_filing", name: "RMD Registration", desc: "", price: 219 });
break;
case "stir_shaken":
services.push({ id: "stir_shaken", name: "STIR/SHAKEN Implementation", desc: "", price: 499 });
break;
case "form_499a":
services.push({ id: "form_499a", name: "Form 499-A Filing", desc: "", price: 499 });
if (s === "red") {
services.push({ id: "dc_agent", name: "D.C. Registered Agent", desc: "", price: 99, priceLabel: "$99/yr" });
}
break;
case "form_499q":
services.push({ id: "form_499q", name: "Form 499-Q Filing", desc: "included with 499-A", price: 0 });
break;
case "cpni_certification":
services.push({ id: "cpni_certification", name: "CPNI Certification", desc: "", price: 149 });
break;
case "cores_registration":
if (s === "red") {
services.push({ id: "cores_registration", name: "FCC CORES Registration", desc: "included", price: 0 });
}
break;
case "bdc_filing":
if (s === "yellow") {
services.push({ id: "bdc_filing", name: "BDC / Form 477 Filing", desc: "", price: 199 });
}
break;
}
}
const ctaContent = document.getElementById("cta-content");
const paymentIcons = document.getElementById("payment-icons");
const reminderCta = document.getElementById("reminder-cta");
if (services.length > 0) {
let html = `<h3 class="text-lg font-bold text-gray-900 mb-4">We can help fix these items</h3>`;
html += `<div class="space-y-2" id="service-list">`;
for (const svc of services) {
const checked = svc.price > 0 && svc.id !== "stir_shaken" ? "checked" : "";
const priceLabel = svc.priceLabel || (svc.price > 0 ? `$${svc.price}` : svc.desc || "included");
html += `<label class="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 cursor-pointer transition">
<input type="checkbox" class="svc-checkbox mt-1 accent-pw-600" data-id="${svc.id}" data-price="${svc.price}" ${checked} />
<div class="flex-1">
<span class="font-medium text-gray-900">${svc.name}</span>
${svc.desc && svc.price > 0 ? `<span class="text-xs text-gray-500 ml-1">${svc.desc}</span>` : ""}
${svc.price === 0 && svc.desc ? `<span class="text-xs text-gray-500 ml-1">(${svc.desc})</span>` : ""}
</div>
<span class="font-semibold text-gray-700 text-sm whitespace-nowrap">${priceLabel}</span>
</label>`;
}
html += `</div>`;
html += `<div id="total-row" class="mt-4 pt-4 border-t border-gray-200"></div>`;
html += `<div class="mt-4 text-center">
<a id="get-started-btn" href="#" class="inline-block bg-orange-500 hover:bg-orange-600 text-white font-bold px-8 py-3 rounded-lg shadow-md transition text-sm">
Get Started &rarr;
</a>
</div>`;
ctaContent.innerHTML = html;
paymentIcons.classList.remove("hidden");
reminderCta.classList.add("hidden");
// Total calculation
function updateTotal() {
const boxes = ctaContent.querySelectorAll(".svc-checkbox");
let total = 0;
let pricedCount = 0;
const selectedIds = [];
boxes.forEach((cb) => {
if (cb.checked) {
const p = parseInt(cb.dataset.price, 10);
selectedIds.push(cb.dataset.id);
if (p > 0) {
// Exclude dc_agent from discount calc
if (cb.dataset.id !== "dc_agent") {
pricedCount++;
}
total += p;
}
}
});
const totalRow = document.getElementById("total-row");
let discountHtml = "";
if (pricedCount >= 2) {
const discount = Math.round(total * 0.15);
const final_ = total - discount;
discountHtml = `<div class="flex justify-between text-sm">
<span class="text-gray-600">Subtotal</span>
<span class="text-gray-600">$${total}</span>
</div>
<div class="flex justify-between text-sm text-green-600 font-medium">
<span>15% bundle discount</span>
<span>-$${discount}</span>
</div>
<div class="flex justify-between text-lg font-bold mt-1">
<span>Total</span>
<span>$${final_}</span>
</div>`;
} else if (pricedCount === 1) {
discountHtml = `<div class="flex justify-between text-sm">
<span class="text-gray-600">Total</span>
<span class="font-semibold">$${total}</span>
</div>
<p class="text-xs text-amber-600 mt-1">Add another service for 15% off</p>`;
} else {
discountHtml = `<div class="flex justify-between text-sm">
<span class="text-gray-600">Total</span>
<span class="font-semibold">$${total}</span>
</div>`;
}
totalRow.innerHTML = discountHtml;
// Update link
const btn = document.getElementById("get-started-btn");
if (btn) {
btn.href = `/order/fcc-compliance?services=${selectedIds.join(",")}&frn=${frn}`;
}
}
ctaContent.querySelectorAll(".svc-checkbox").forEach((cb) => {
cb.addEventListener("change", updateTotal);
});
updateTotal();
} else {
ctaContent.innerHTML = `<h3 class="text-lg font-bold text-green-800 mb-2">Looking good &mdash; all checks passed</h3>
<p class="text-sm text-gray-600">Your filings appear current. No action is needed at this time.</p>`;
paymentIcons.classList.add("hidden");
reminderCta.classList.remove("hidden");
}
}
// --- Auto-fill from URL ---
(function init() {
const params = new URLSearchParams(window.location.search);
const frn = params.get("frn");
const entity = params.get("entity");
if (frn) {
frnInput.value = frn;
runCheck();
} else if (entity) {
nameInput.value = entity;
runNameSearch();
}
})();
</script>
</Base>