Commit graph

389 commits

Author SHA1 Message Date
justin
426fbb2ea1 docs(plan): mark all fulfillment phases complete + validated 2026-06-02 03:38:47 -05:00
justin
4b6c828b1c feat(campaigns): deficiency-flag segments + LP routing (Phase 5)
Add 6 flag-based campaign segments to build_trucking_campaigns.py keyed off
fmcsa_carriers.deficiency_flags: for_hire_boc3, irp_ifta, intrastate_authority,
state_weight_tax (per-state LP), state_emissions (CA->ca-mcp-carb), hazmat.
Each injects an order-LP link into subscriber attribs (lp_link) and only
schedules when its CAMPAIGN_*_ID source template env is set (nightly run never
breaks on unconfigured templates). Adds --list-segments audience report and a
synthetic-data segment test (fixed a real psycopg2 % escaping bug in LIKE).
2026-06-02 03:38:02 -05:00
justin
fc1a0588f7 feat(advisory): prerequisite-aware DOT lookup + state recommendations
- DOT lookup now returns prerequisite_status {usdot_active, authority_active,
  authority_pending} from live FMCSA data so the order flow can advise
  sequencing BEFORE a customer places an order.
- State-requirements recommendations annotated with prerequisite + label
  (e.g. IRP/IFTA/state taxes need an active USDOT) for UI warnings.
2026-06-02 03:34:40 -05:00
justin
3322003da0 feat(order-pages): landing pages for all state/hazmat/emissions slugs
Add /order/{irp-registration,ifta-application,ifta-quarterly,or-weight-mile-tax,
ny-hut-registration,ky-kyu-registration,nm-weight-distance,ct-highway-use-fee,
ca-mcp-carb,state-dot-registration,intrastate-authority,osow-permit,
state-trucking-bundle,hazmat-phmsa,state-emissions,usdot-reactivation}.
Each renders the slug-gated state-trucking intake wizard. Site builds 48 pages,
new routes verified to render correct intake sections.
2026-06-02 03:33:23 -05:00
justin
63a28f99de feat(pipeline): FMCSA activation gating (require_active edges)
Dependency edges can now require the prerequisite be ACTIVE at FMCSA, not
just our handler completed. mc-authority/ucr/d&a now wait for an active
USDOT; BOC-3 stays parallel-OK (can file while authority pending). Adds
_prerequisite_active() polling FMCSA QC API, a waiting_on_activation hold
state with next-check timestamp, and a 21-day authority vetting estimate
for customer comms. Branch logic unit-tested.
2026-06-02 03:32:37 -05:00
justin
bbbfeaeaa1 feat(boc3): authority-aware filing with upsell-approve follow-ups
_get_authority_state() returns structured FMCSA authority state; handle()
branches on active/pending/revoked/none:
- active: file/refresh BOC-3 (current behavior)
- pending: file BOC-3 + insurance/21-day-vetting reminder
- revoked: file + recommend reinstatement (mc-authority, never auto-charge)
- none (USDOT only): flag MC authority needed first, do not file blindly
recommended_followups + authority_state persisted in admin todo for
upsell-approve on the order timeline.
2026-06-02 03:31:17 -05:00
justin
cadff79bd6 test(fulfillment): consistency + intake-completeness checker
Cross-references every DOT/state/hazmat slug across COMPLIANCE_SERVICES,
REQUIRED_FIELDS, SERVICE_META, INTAKE_MANIFEST, and SERVICE_HANDLERS, and
verifies every required field is collectible by its assigned intake steps.
Caught + fixed missing usdot-reactivation SERVICE_META entry. 24/24 pass.
2026-06-02 03:29:53 -05:00
justin
9c6b8d95e0 feat(fulfillment): state-trucking intake form + hazmat/emissions products
- Add StateTruckingIntakeStep.astro with slug-gated sections (IRP/IFTA,
  emissions, intrastate authority, OSOW, hazmat/PHMSA); wired into Wizard
- Register hazmat-phmsa + state-emissions products & SERVICE_INFO
- Add server-side bundle/mutual-exclusion enforcement + REQUIRED_FIELDS
- State-trucking slugs now collect real intake data (were review-only)
- Surface slug-specific intake fields in admin todo (_summarize_intake)
- Remove state slugs from email ADMIN_ASSISTED set (now get intake links)
2026-06-02 03:27:51 -05:00
justin
71b888f993 Support FMCSA add date format for new carrier targeting 2026-06-01 20:24:58 -05:00
justin
766cfcd671 Tighten new carrier campaign recency filter 2026-06-01 20:23:49 -05:00
justin
4f4edb5f00 Add new carrier startup campaign targeting 2026-06-01 20:19:55 -05:00
justin
2232570c9f Add trucking state authorization plan 2026-06-01 20:14:36 -05:00
justin
a2aaac0066 Document trucking state campaign fulfillment requirements 2026-06-01 20:06:53 -05:00
justin
8485bba51d Add trucking campaign setup script 2026-06-01 20:00:46 -05:00
justin
776c664df8 Exclude Yahoo/AOL domains from trucking campaign builder 2026-06-01 17:07:37 -05:00
justin
3d611e97a4 tawk mobile UX: hide widget on small screens to stop text overlay popups
Adds Tawk_API.onLoad mobile guard (max-width 768px -> hideWidget) in shared
footer snippet and current built pages so mobile browsers no longer get the
proactive text bubble covering content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:31:01 -05:00
justin
02112facf5 capture client signature before filing signed DOT forms
Forms that legally require the client's signature were not being captured
correctly:

- MCS-150 handler created a perjury e-sign record but then submitted to FMCSA
  anyway, before the client signed. Now it gates submission: request the
  signature, hold, and only file when handle_esign_completed re-dispatches with
  client_approved=True.
- MCS-150 e-sign links were signed with JWT_SECRET/ADMIN_JWT_SECRET, but the
  portal verifies with CUSTOMER_JWT_SECRET, so every link returned "Invalid
  portal link." New shared dot_esign helper signs with CUSTOMER_JWT_SECRET.
- carrier-closeout (final MCS-150 Out of Business) and entity-dissolution
  (Articles of Dissolution + no-lawsuits/liens/judgments attestation) captured
  no signature at all. Both now request a signed attestation before the
  workflow proceeds.
- mc-authority / emergency-temporary-authority now get a correctly labeled
  OP-1 applicant certification instead of an "MCS-150" record.

Also fixes a latent dispatcher bug: order["service_slug"] was never set, so
handlers sharing a class fell back to their default SERVICE_SLUG. This made
entity-dissolution run the carrier-closeout branch and mc-authority/etc. look
like mcs150-update. Now the resolved slug is injected into order_data.

Portal e-sign page now renders the document-specific certification text from
metadata.perjury_text (so the dissolution no-liabilities attestation and OP-1
cert are actually shown to the signer), not just a generic perjury line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:30:09 -05:00
justin
869bcac287 fix batch SO item_code (use erpnext_item) + notification surcharge breakdown
ROOT CAUSE of orders never fulfilling: the batch Sales Order used the service
SLUG as item_code (e.g. 'mcs150-update') but ERPNext items use the catalog
erpnext_item codes ('MCS150-UPDATE'), so SO creation threw 'Item not found' ->
no SO -> no portal -> no fulfillment. Now maps slug -> erpnext_item (falls back
to COMPLIANCE-SERVICE). DOT ERPNext items were also missing — created them.

Notification: show Subtotal / Discount / Card surcharge / Total so totals like
$35.54 (= $34.50 + $1.04 surcharge) are transparent instead of looking wrong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:01:29 -05:00
justin
f4230e1cb1 intake email: DOT services now send a customer intake-form link (auto)
Federal DOT services (MCS-150, BOC-3, UCR, authority, D&A, audit, full-compliance,
reactivation, ETA, closeout) now have customer intake pages, so they get an
intake-form link like FCC services instead of the old 'admin-assisted / we're
working on it' message. Only form-less state-level filings stay admin-assisted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:54:43 -05:00
justin
f9c4f6783b fix Telegram order alert: aggregate whole batch (total + all services)
Was reading only updated.rows[0] -> reported a single line item's net as the
'Total' and showed just one service for multi-service batches (e.g. Paul Wilson's
3-service $218 PayPal batch showed as 'mcs150-update $34.50'). Now sums
service_fee - discount + surcharge + gov_fee across all rows and lists every
service.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:46:35 -05:00
justin
2fab98c0a8 postfix: multi-IP warmup sending pool (20 IPs, gradual rotation)
- 20 IPs (.90-.109 / mta01-mta20) with FCrDNS + SPF in HestiaCP
- .90 (mta01) dedicated Yahoo/AOL recovery IP (yahooslow, 20s trickle)
- .91-.109 (out02-out20) rotation pool via transport_maps randmap
- pw-mta-warmup: cron-driven scheduler grows the active rotation pool
  3 -> 5 -> 8 -> 12 -> 16 -> 19 IPs over ~25 days
- mta_setup.sh: idempotent installer (backups + postfix-check-gated reload)

New IPs verified clean on Spamhaus/Barracuda/SpamCop/SORBS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 19:03:30 -05:00
justin
6def0f6186 collect photo ID for all FMCSA filings that legally require the signer's ID
Gap: dot-registration (new USDOT=MCS-150) routed through intake but never asked
for photo ID; usdot-reactivation, emergency-temporary-authority, carrier-closeout
(final MCS-150 + authority revocation), entity-dissolution, and entity-upgrade-
bundle weren't wired to collect it at all.

- intake_manifest: route usdot-reactivation, ETA, carrier-closeout,
  entity-dissolution through the dot-intake step
- DOTIntakeStep DOT_SECTIONS: add dot-sec-photo-id for dot-registration and all
  the above (operations + photo ID)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:00:59 -05:00
justin
17b16087a4 checker sell-trucks: add Company name field prefilled from the carrier record
Separate from the contact's first/last name; defaults to data.legal_name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:53:16 -05:00
justin
ae52c63983 add tawk.to live chat to 8 order/tool pages that were missing it
dot-compliance, trucking-new-carrier, neca-ocn, fcc-carrier-registration,
corporation-check, identity-complete, state-puc, fcc-499q.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:46:49 -05:00
justin
07e2f34608 dot-compliance: mutually-exclusive service conflicts + hero copy/layout
- Auto-uncheck conflicting services: closing-down (carrier-closeout, entity-
  dissolution) vs any operational filing; new USDOT vs reactivation; new USDOT
  vs MCS-150 update.
- Hero: removed 'this is all we do' (we also do telecom); 4-col grid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:48 -05:00
justin
d3bf5b3520 preview test send: keep {{ UnsubscribeURL }} (real link); hero 4-col to save vertical space
- send_test no longer overwrites {{ UnsubscribeURL }} with a dead static URL;
  Listmonk renders it into a working per-subscriber unsubscribe link.
- dot-compliance hero grid: 4 columns (minmax 150px, max-width 920px) instead
  of 3 to reduce vertical space.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:38:57 -05:00
justin
60312e5201 campaign builder: add --preview mode + fix subscriber-attach + test-send list bugs
- import_subscribers: was POSTing wrong bulk shape AND fallback used 'list_ids'
  (ignored by Listmonk) instead of 'lists' -> subscribers never attached to the
  list -> real sends would go to an empty list. Now single-adds with 'lists',
  handles already-exists, returns a count, logs if 0 added.
- send_test: passed base['lists'] (objects) instead of IDs -> test send rejected.
  Now extracts list IDs.
- create_and_schedule_campaign: add schedule= flag (preview makes drafts).
- --preview: 1 sample carrier/campaign, only owner email, drafts not scheduled,
  test sends immediately, never marks real carriers sent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:28:09 -05:00
justin
03702dfbb7 campaign builder: send test email to carrierone@gmx.com per campaign before real blast
Each of the 8 daily campaigns gets a test send immediately after creation
using the first row's real carrier data as the sample. TEST_EMAIL env var
overrides the default (carrierone@gmx.com).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:19:28 -05:00
justin
b66b5a4cdc dot-compliance: expand hero with PW specialty, speed, and customer service
4-card dark hero: specialized in trucking compliance, fast turnaround (1-2 days),
attention to detail (verified against current FMCSA reqs), real people/support.
Trust bar updated: No Login.gov required + Klarna added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:17:17 -05:00
justin
659f257167 dot-compliance order: add Emergency Temporary Authority ($499) + USDOT Reactivation ($149) cards
These were missing — the ETA button in email 188 linked to the order page
with services=emergency-temporary-authority but no matching checkbox existed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:12:20 -05:00
justin
dcd9fb61d0 migration 083: use CREATE INDEX CONCURRENTLY to avoid locking fmcsa_carriers
The original CREATE INDEX (non-concurrent) on a 2M-row table held a SHARE lock
for ~33 minutes, blocking all 25+ DOT checker queries and causing 'Failed to
fetch' for real users. CONCURRENTLY builds the index without a table lock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:42:47 -05:00
justin
13492af732 dot-lookup: fix hanging FMCSA fetch with AbortController (not AbortSignal.timeout)
AbortSignal.timeout() requires Node 17.3+. The API container likely runs an
older Node version, so timeouts never fired -> fetch hung forever when FMCSA
API is down -> nginx proxy timeout -> 'Failed to fetch' in the browser.

Fix: use AbortController + manual setTimeout() which works on all Node versions.
All 3 external fetch points (fmcsaFetch x2, SOS x2) now actually abort at 5s.

Also: guard final res.json() with !res.headersSent so the 12s deadline fallback
and the normal response path can't double-send.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:36:28 -05:00
justin
cebc432af8 dot-lookup: add 12s hard deadline + reduce FMCSA timeout to 5s
If FMCSA live API is slow (can take 2x 10s = 20s when down), the route would
hang until nginx proxy killed the connection -> 'Failed to fetch'. Now:
- fmcsaFetch timeout: 10s -> 5s (two calls max 10s total)
- SOS entity-status timeout: already reduced to 5s
- 12s hard deadline: if any live API hangs past 12s, immediately return
  census-only data with a 'partial=true' flag so the user gets something

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:34:04 -05:00
justin
e7afc3002e dot-lookup: revert fmcsaFetch wrapping (already null-safe), keep 5s SOS timeout
The bare catch{} introduced a control-flow issue. fmcsaFetch() already returns
null on all errors and never throws, so the try/catch wrapping was unnecessary.
Keep only the SOS timeout reduction (20s->5s) as the actual fix for the nginx
proxy timeout that caused 'Failed to fetch' on slow DOT lookups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:28:59 -05:00
justin
e0be6468d4 dot-lookup: fix 'Failed to fetch' caused by SOS timeout exceeding nginx proxy limit
Two WORKER_URL/entity-status calls both had 20s timeouts; worst case 40s total
response time exceeds nginx proxy_read_timeout, dropping the connection and
causing the browser to show 'Failed to fetch'. Also wraps fmcsaFetch calls
explicitly so FMCSA API failure still returns full local census data.

- AbortSignal.timeout(20000) -> 5000 on both SOS entity-status calls
- fmcsaFetch carrier + authority calls wrapped in individual try/catch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:19:10 -05:00
justin
0b7a35a58e trucking campaigns: daily builder + MX verifier concurrency + tracking column
- build_trucking_campaigns.py: nightly script that creates 8 Listmonk campaigns
  per day (4 TZ x 2 types: MCS-150 overdue 2k/TZ, inactive USDOT 1k/TZ)
  at 4AM ET / 5AM ET (CT) / 6AM ET (MT) / 7AM ET (PT). Deduplicates via
  listmonk_sent_at column.
- migration 083: add listmonk_sent_at + listmonk_campaign_type to fmcsa_carriers
- email_verifier.py: bump max_workers from 5 to 20 for 4x faster throughput
- cron: daily pw-trucking-campaigns at 08:00 UTC (3 AM EST)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:07:44 -05:00
justin
54a46062a5 review fixes: wrap-up checkout dead-end, confident entity flag, textarea style
- order/dot-compliance: add carrier-closeout ($199) + entity-dissolution ($49)
  cards so the checker's wrap-up CTA actually resolves (was a dead-end: no
  matching data-slug checkbox to pre-select)
- new-carrier flag: drop the 'not tax advice / we'll confirm' hedge, reframe
  confidently as a fee/cost point (not taxes); only show the Wyoming-LLC caveat
  when Wyoming is actually selected
- checker: fix malformed inline style on the sell-truck textarea (missing ;)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 02:02:20 -05:00
justin
d85fdd36d9 new-carrier wizard: flag corp-favorable states (CA/TN/NY) in the entity step
When forming or operating in CA (gross-receipts fee >$250k), TN ($300 vs $20
report), or NY (LLC publication + filing fee), show an advisory with the reason
and a one-click 'Use a corporation'. Keyed off wiz.entityState/baseState.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:47:42 -05:00
justin
ffe6f34bc8 wrap-up: reframe as done-for-you (we file everything; client only cancels insurance etc.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:25:58 -05:00
justin
374db59ba7 checker: add 'not selling' sell-trucks option; remove em dashes from trucking pages
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:21:23 -05:00
justin
c98f48848c entity dissolution: $49 add-on to wrap-up + state fees (was $199)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:13:19 -05:00
justin
b25d1f5fd3 trucking wrap-up: close-out becomes a paid order + workflow
- Checker closing mode now pitches a done-for-you 'Trucking Wrap-Up' ($199)
  with a buy button to /order/dot-compliance?services=carrier-closeout, instead
  of a lead form. DIY checklist replaced by what's-included list.
- Entity dissolution offered as a paid add-on with the lawsuits/liens/judgments
  warning before dissolving.
- New catalog services: carrier-closeout ($199), entity-dissolution ($199).
- CarrierCloseoutHandler orchestrates the sequential shutdown workflow
  (final MCS-150 out-of-business, MC revoke, UCR cancel, IFTA/IRP + state
  closures; dissolution branch for the add-on) as admin-tracked tasks.
- Sell-your-trucks: single shared form with quick-cash / marketplace / both;
  name field is now a real first+last name (no corp-name prefill).
- tickets categories: add truck_sale_both, drop business_closeout (now an order).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 01:01:02 -05:00
justin
1e34707258 migration 082: widen tickets.category CHECK for lead-capture categories
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:50:32 -05:00
justin
0409167e5a tickets API: allow insurance_lead + business_closeout + truck_sale categories
These lead-capture categories were posted by the DOT checker but missing from
VALID_CATEGORIES, so the API rejected them with 400 (insurance_lead too — it
was referenced in the Telegram code but never allowlisted).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:49:18 -05:00
justin
5f3a9dc54f web: remove CB 10-codes and ByeTruck name from checker + new-carrier page
- 10-code jokes are email-only now; revert checker/new-carrier CTAs to plain
- Sell-trucks quick-cash flow no longer names ByeTruck or links out; lead still
  routes via the truck_sale_quickcash ticket (internal routing stays generic)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:48:12 -05:00
justin
0835358cd4 add resized 536px JPEG banner for trucking emails (lighter, fits display 1:1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:43:58 -05:00
justin
d9ffda27a4 trucking CTAs: CB 10-codes in buttons + compliance-check button on new-carrier page
- DOT checker: '10-4' on Fix My DOT Filings / Get These Handled; '10-7'
  (out of service) on the business close-out CTA
- New-carrier (LLC) page: add a free Compliance Check button in the hero

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:07:55 -05:00
justin
c0021c3cd6 DOT checker: add business close-out mode + sell-your-trucks routing
- Intent toggle: 'staying in business' (compliance) vs 'closing my business'
- Closing mode shows a green, personalized wind-down checklist from FMCSA data
  (final MCS-150/USDOT deactivation, MC revoke, UCR, IFTA/IRP, state permits,
  insurance, entity dissolution) + 'let us handle the shutdown' lead capture
- Sell-your-trucks box: quick cash -> ByeTruck referral lead + handoff;
  marketplace -> email capture for follow-up guide
- Deep link via ?intent=closing for the email CTAs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:57:44 -05:00
justin
df48ef786d add CB radio accessories banner image for trucking email campaigns
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:43:36 -05:00
justin
2f635227aa improve name-check: match base name without entity suffix (LLC/Inc/Corp), flag close matches 2026-05-30 23:07:52 -05:00