16 KiB
Plan
Status: COMPLETE (all phases implemented + validated 2026-06-02)
| Phase | What | Status |
|---|---|---|
| 1 | Hazmat/PHMSA handler + product (hazmat-phmsa, $149) |
✅ |
| 1.5 | Order-form bundle/mutual-exclusion enforcement (server-side) | ✅ |
| 2 | State-trucking intake form (slug-gated) + REQUIRED_FIELDS + admin-todo fields | ✅ |
| 2.5 | BOC-3 authority-aware (active/pending/revoked/none) + upsell-approve follow-ups | ✅ |
| 2.6 | Pipeline activation gating (require_active edges, FMCSA poll, waiting_on_activation) |
✅ |
| 3 | State emissions (non-CA) product state-emissions ($199) |
✅ |
| 4 | Order landing pages for all state/hazmat/emissions slugs (48 pages build) | ✅ |
| Adv | Prerequisite-aware DOT lookup + state recommendations | ✅ |
| 5 | Campaign builder deficiency segments + LP routing + --list-segments | ✅ |
| Val | Consistency checker (24/24) + campaign segment test (synthetic) | ✅ |
Remaining ops (not code): create the 6 Listmonk source-campaign templates and
set their CAMPAIGN_*_ID envs (CAMPAIGN_FOR_HIRE_ID, CAMPAIGN_IRP_IFTA_ID,
CAMPAIGN_INTRASTATE_ID, CAMPAIGN_WEIGHT_TAX_ID, CAMPAIGN_EMISSIONS_ID,
CAMPAIGN_HAZMAT_ID). Until set, those segments are reported by
--list-segments but skipped by the scheduled run. Optional follow-up: a
client-side incompatibility UX hint in the order form (server already enforces).
Goal
Make every trucker deficiency type we flag actually fulfillable: each flagged deficiency must have (1) a service handler that can complete the work, (2) a checkout/order path, and (3) an intake form that collects all information that handler needs before the job runs. Only after fulfillment is complete and verified do we extend the campaign builder to email those deficiency segments.
Scope / affected areas
Deficiency flags in play (live counts): for_hire (19,811), interstate_irp_ifta (19,761), intrastate_authority (14,081), state_emissions (12,424), state_weight_tax (6,289), state_permit (3,418), mcs150_overdue (4,539), hazmat (514), zero_fleet (134).
Files / systems:
scripts/workers/services/__init__.py— handler registryscripts/workers/services/state_trucking.py— IRP/IFTA/weight-tax/permit/intrastate handler (admin-todo only today)scripts/workers/services/mcs150_update.py— MCS-150 + reactivation (real FMCSA filing)scripts/workers/services/boc3_filing.py— BOC-3 (Playwright)- NEW
scripts/workers/services/hazmat_phmsa.py— only fully-missing fulfillment path site/src/lib/intake_manifest.ts— per-service intake steps + pricing/metasite/src/components/intake/steps/DOTIntakeStep.astro— unified DOT intake (no state-trucking sections today)- NEW
site/src/components/intake/steps/StateTruckingIntakeStep.astro— state filing fields api/src/routes/compliance-orders.ts—COMPLIANCE_SERVICES(products) +REQUIRED_FIELDS(validation; none defined for any DOT/state-trucking slug today)api/src/routes/checkout.ts— slug allowlistapi/src/routes/dot-lookup.ts— recommended-services mapping- NEW
site/src/pages/order/*.astro— landing pages for state-trucking + hazmat (none exist) scripts/build_trucking_campaigns.py— campaign builder (extend last)
Key findings (grounding)
- Fulfillment handlers already exist for ~98% of flags. The single fully missing path is hazmat / PHMSA registration (no handler, product, page).
- State-trucking intake collects nothing. All 13 state slugs map to
["review"]inintake_manifest.tswith a comment "info collected at checkout" — but checkout collects no per-filing fields. So IRP has no vehicle/weight/jurisdiction data, IFTA has no fleet/base-state, NY HUT has no vehicle list, intrastate-authority has no insurance/authority-type, etc. The handler's admin todo is therefore incomplete and an admin must chase the customer for data. REQUIRED_FIELDShas zero entries for any DOT or state-trucking slug, so the API performs no intake validation for these orders.- No dedicated order landing pages for IRP/IFTA/state-tax/permit/intrastate or hazmat. Checkout works by slug, but campaign emails have no clean LP to drive conversions.
- State emissions flags (NY/CO/MD/NJ/MA/etc., 12,424) only map to a product
for CA (CARB via
ca-mcp-carb). Non-CA emissions have no product — decide whether to build or fold into existing state DOT/permit service.
Approach (concrete ordered steps)
Phase 1 — Close the hazmat fulfillment gap (only fully-missing path)
- Add
HazmatPHMSAHandlerinscripts/workers/services/hazmat_phmsa.py(admin-assisted, mirrorsstate_trucking.py: creates admin_todo with PHMSA registration steps, sends status email). Slughazmat-phmsa. - Register
"hazmat-phmsa": HazmatPHMSAHandlerinservices/__init__.py. - Add product to
COMPLIANCE_SERVICES, meta tointake_manifest.ts, slug to checkout allowlist.
Phase 1.5 — Order-form incompatibility enforcement (bundles vs components, dupes)
Today the batch endpoint (compliance-orders.ts POST /batch) only dedupes and
hard-codes one case (drop standalone fcc-499a when fcc-499a-499q present).
There is no general rule preventing a customer from selecting a bundle plus its
own components (e.g. dot-full-compliance + mcs150-update + boc3-filing, or
state-trucking-bundle + irp-registration), or other incompatible combos. This
double-charges and creates duplicate filings.
Build a single source of truth for service relationships and enforce it:
- Add
BUNDLE_COMPONENTSmap (bundle slug -> component slugs) coveringdot-full-compliance,state-trucking-bundle,new-carrier-bundle,fcc-full-compliance, plus the DBservice_bundlesrows. - Add
INCOMPATIBLE_PAIRS/ mutually-exclusive groups (e.g.usdot-reactivationvscarrier-closeout;fcc-499avsfcc-499a-499q; emergency-temp-authority vs mc-authority where applicable). - Server-side (authoritative) in
/batch: when a bundle is present, drop any of its components from the cart (keep the bundle), reject hard-incompatible pairs with a clear error, and keep dedup. Generalize the existing 499a special-case into this map. - Client-side (order form / cart UI): disable/grey out a component when its parent bundle is selected (and vice-versa: selecting all components suggests the bundle), and prevent selecting mutually-exclusive options, with an inline explanation. Mirror the server map so UX matches enforcement.
Phase 2 — Make state-trucking intake actually collect required data
- Build
StateTruckingIntakeStep.astro(one shared step, sections shown by slug, mirroringDOTIntakeStep.astro's section-gating pattern):- Carrier identity: legal name, DOT#, MC#, base state, contact (prefill from DOT lookup).
- IRP/IFTA: power units w/ VIN+plate+gross-weight rows, operating jurisdictions, fuel type.
- Weight-distance (OR/NY/KY/NM/CT): vehicle list + gross weights + (OR) declared combined weight.
- CA MCP+CARB: fleet engine model-years for CARB Clean Truck Check, CA# if any.
- Intrastate authority: authority type, insurance carrier + policy#, cargo, BOC-3 on file?
- State DOT / OSOW: as needed.
- Update
intake_manifest.ts: replace["review"]with["state-trucking", "review"]for the 13 slugs; wire the step into the Wizard. - Add
REQUIRED_FIELDSentries incompliance-orders.tsfor each state-trucking slug + the DOT slugs (mcs150, ucr, boc3, dot-registration, mc-authority, etc.) so intake is validated server-side. Mirror handler "Intake data needed" docstrings. - Update
state_trucking.pyhandler to read + surface the new intake fields in the admin todo (so admins get vehicle lists, jurisdictions, insurance, etc.).
Phase 2.5 — Make BOC-3 authority-aware (preexisting authority handling)
The BOC-3 attaches to a carrier's operating authority (MC/FF/MX docket). Today
boc3_filing.py only reads commonAuthorityStatus/contractAuthorityStatus/
brokerAuthorityStatus to print a status string and otherwise always files a
fresh BOC-3. That can be wrong/wasteful depending on the preexisting authority.
Add branching off the live FMCSA authority state:
- Active authority: file/refresh BOC-3 only. (current behavior)
- Pending authority: file BOC-3 + flag that active insurance must be on file for the authority to activate; create follow-up todo.
- Revoked/inactive authority: file BOC-3 and flag/upsell reinstatement
(OP-1 reinstatement + $80 gov fee, route via
usdot-reactivation/mc-authorityreinstatement branch). BOC-3 alone does not reinstate. - No authority (USDOT only): BOC-3 has nothing to attach to — flag that MC
authority (
mc-authority) is likely needed first; do not silently file. Implementation: haveprocess()/handle()read full authority status (reuse_check_boc3_status, expanded to return structured fields), select the branch, adjust the admin-todo + customer email, and emit arecommended_followupslist the order timeline / upsell can surface. No automatic charge for the follow-up — surface it for the customer/admin to approve.
Phase 2.6 — Prerequisite/activation gating (wait for FMCSA "active", not just "submitted")
There are real FMCSA dependency chains where a downstream filing must wait for an
upstream item to be active at the agency, not merely submitted by us. The
existing pipeline_orchestrator.py models ordering via wait_for, but a step is
treated as satisfied when pipeline_step_status == "completed" (= our handler
finished), which is NOT the same as FMCSA showing it active.
True prerequisites to enforce:
- MC Authority (OP-1) requires an active USDOT.
- Authority activation requires BOC-3 on file + insurance (BMC-91) on file, then a mandatory ~21-day vetting/protest period before it goes active. BOC-3 + insurance CAN be filed while authority is pending (parallel OK).
- IRP / IFTA / intrastate-authority / UCR that depend on active authority (or active USDOT) must wait for that activation, not just for the prior order to be submitted.
Implementation:
- Add an "activation gate" to the orchestrator: for dependency edges flagged
require_active: true, poll FMCSA (mobile QC API for USDOT status; L&I for authority/BOC-3/insurance) and only mark the dependency satisfied when the agency reports active. Until then, hold the downstream step in awaiting_on_activationstate with a next-poll timestamp. - Encode the 21-day authority vetting window as an expected-activation estimate so the timeline/customer comms set correct expectations.
- Expand
PIPELINESedges withrequire_activeflags (USDOT→MC, USDOT→IRP/IFTA/ UCR/intrastate, authority-active→IRP-for-hire/intrastate). - Standalone (non-bundle) orders: when a single service is ordered whose prerequisite isn't active yet, surface a clear "blocked until X is active" status + recommended prerequisite order rather than filing prematurely.
Phase 3 — Decide + handle state emissions (non-CA)
- Either: (a) add a generic
state-emissionsservice handler+product covering NY/CO/MD/NJ/MA clean-truck/ACT programs, or (b) map those flags tostate-dot-registration/ advisory-only. (Open question — see below.)
Phase 4 — Order landing pages
- Create
site/src/pages/order/*.astrofor: irp-ifta (combined), state weight taxes (one templated page per state or a single state-aware page), ca-mcp-carb, intrastate-authority, hazmat-phmsa. Reuse existing order-page layout (e.g.boc3-filing.astro,ucr-registration.astroas templates).
Phase 5 — Extend campaign builder (only after 1-4 verified)
- Add new campaign segments to
build_trucking_campaigns.pykeyed offdeficiency_flags: for-hire/BOC-3+UCR, IRP/IFTA, intrastate-authority, state weight-tax (per-state), CA MCP/CARB, hazmat. Each links to its LP. - Create the corresponding Listmonk source campaigns (templates) and wire IDs.
Validation (how each part is verified)
- Handlers: unit-invoke each handler with a synthetic
order_datadict in a throwaway script; assert anadmin_todosrow is created with the expected fields and (dev mode) no live filing fires. ConfirmSERVICE_HANDLERSresolves every new slug. - Intake completeness (the core ask): write a check that, for every slug,
cross-references
REQUIRED_FIELDS[slug]against the fields the intake step emits and against the keys the handler reads — fail if a handler-needed field is never collected. This is the verifiable "we collect all needed info" metric. - Checkout/products: assert every flagged slug exists in
COMPLIANCE_SERVICES,SERVICE_META, checkout allowlist, andSERVICE_HANDLERS(one consistency test). - Order pages: build site (
astro build/ existing build script) and confirm each new/order/<slug>route renders; smoke-load locally. - Campaign builder: run
build_trucking_campaigns.py --dry-runand assert each new segment selects a nonzero, deduped audience pointing at a valid LP. - End-to-end: place a test order per new slug through checkout in dev, verify intake validation blocks missing fields, and the handler produces a complete admin todo.
Open questions / decisions
RESOLVED (per user 2026-06-02):
- BOC-3 follow-ups + prerequisite blockers → upsell-approve, advise pre-order
where possible. Two layers:
- Pre-order advisory (preferred): extend the existing DOT Compliance Check
tool (
site/public/tools/dot-compliance-check/) +dot-lookuprecommended services to be prerequisite-aware. When a recommended service needs an active prerequisite (e.g. IRP needs active authority), show the dependency, pre-select the prerequisite, order the cart correctly, and state the ~21-day authority activation expectation — all before payment. - Post-order upsell-approve: when a handler discovers a blocker mid-fulfillment
(e.g. BOC-3 ordered but authority revoked → needs reinstatement), write a
recommended_followupsentry on the order and render a one-click, pre-filled "Add this service" card on the order timeline/portal. Customer confirms + pays via the existing checkout. No auto-charge. - Standalone checkout guard: if a service is bought directly and its prereq isn't active at FMCSA, warn + offer to add the prereq rather than filing something unfulfillable.
- Pre-order advisory (preferred): extend the existing DOT Compliance Check
tool (
- State emissions (non-CA): build a real product. Add a
state-emissionsservice (handler + product + intake + page) covering NY/CO/MD/NJ/MA clean-truck / Advanced Clean Trucks programs (CA stays onca-mcp-carb). - Pre-order advisory: yes. Covered by 1 above — reuse the compliance-check tool as the advisory surface.
STILL OPEN:
4. Pricing for hazmat-phmsa and state-emissions.
DEFAULT: hazmat-phmsa = $149 (admin-assisted PHMSA registration; gov fee
$25 placardable-hazmat reg billed at cost). state-emissions = $199
(NY/CO/MD/NJ/MA clean-truck / ACT advisory + registration assist).
5. Vehicle-list intake fidelity.
DEFAULT: lightweight up front — collect fleet count + base/operating states
- fuel type + gross-weight bracket at intake; collect full per-vehicle VIN/plate/weight rows via a post-order follow-up form only when the specific filing requires it (IRP, weight-distance taxes). Keeps conversion high.
- Standalone-order prereq guard. DEFAULT: warn + offer to add the prerequisite (pre-selected), allow override ("file anyway"). Never a hard block.
- Long-term home for the reviewable plan (
docs/plans/used here; noside_paneltool in this harness).