- 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.
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
- Checks state SOS for entity name availability (via workers adapter)
- Checks FMCSA census for exact + fuzzy name matches
- Returns: sos_available, fmcsa_in_use, fmcsa_matches, combined 'available' flag
- 20s timeout on SOS lookup, FMCSA query is instant (local DB)
- OOS (Out of Service): RED, urgent messaging, maps to Emergency Temporary Authority $499
- Inactive: YELLOW, reactivation $149
- Not Authorized: YELLOW, new USDOT + MC authority
- Revoked: RED, new authority application
- Suspended: YELLOW, compliance resolution + reinstatement
- Cancelled: YELLOW, new registration
- Each status has specific actionable messaging with PW CTA
- New service: usdot-reactivation — filed via ask.fmcsa.dot.gov (sub-cat 302)
- Inactive carriers see 'PW can handle reactivation, no Login.gov needed'
- Compliance checker maps inactive operating status to reactivation service
- Formal entities: queries workers /entity-status endpoint for real-time
Secretary of State status (ACTIVE/dissolved/revoked/delinquent)
- Green if active, red if not active, yellow if not found or lookup failed
- Sole proprietors: yellow 'form an LLC' upsell
- 12s timeout so compliance check doesn't hang on slow state portals
- Client-side: Laplacian variance blur detection in photo quality check
(very blurry / somewhat blurry / acceptable / good)
- Server-side: async Ollama vision model validates uploaded image is a
real government ID (minicpm-v:8b), flags non-ID uploads
- Corporate check: sole proprietors now get yellow 'form an LLC' upsell,
formal entities get annual report/RA reminder
- filing_attestation.py: generates cover page attesting PW submitted document
to recipient with date/time stamp, contact info, and digital signature
- fax_sender.py: sends PDFs via VitalPBX API, polls for delivery, generates
attested copy for customer records
- dot-lookup.ts: if DOT has pending MCS-150 order, show green 'UPDATE SUBMITTED'
instead of red 'OVERDUE' in compliance checker
- requirements.txt: add pyhanko + cryptography for PDF digital signatures
- id-upload.ts: replace broken direct minio import with workers presign/upload
- job_server.py: add minio-upload handler for API to store files via workers
- rewrite presigned URLs from internal minio:9000 to public minio.performancewest.net
- fixes: thumbnail not showing after phone upload, base64 fallback storage
Mobile cameras produce 8-12MB photos. Now:
- Canvas-based resize to max 2000x1500 before upload
- JPEG compression at 0.7-0.85 quality
- Express body limit increased to 5MB for id-upload route
- Falls back to raw upload for small images and PDFs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tickets: 📩 for support, 🏥 for insurance leads, 💰 for quotes
Quotes: 💰 with name, email, company, service, details
All fire-and-forget to Telegram bot — non-blocking.
Previously these only went to ERPNext with no real-time alert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- API: POST /api/v1/id-upload/token generates upload token
- API: POST /api/v1/id-upload/:token receives base64 image, stores in MinIO
- API: GET /api/v1/id-upload/:token/status returns upload status + thumbnail
- Mobile page: sends image as base64 with upload_token
- Desktop intake: requests token, generates QR with upload URL, polls
every 3s for phone upload, auto-shows thumbnail when detected
- MinIO storage with presigned URLs for thumbnails
- Compliance order intake_data updated with photo_id_uploaded flag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Intake data now persists to DB after each step completion (non-blocking).
If browser crashes, data is recoverable from compliance_orders.intake_data.
Partial saves (_partial: true) only update intake_data without changing
payment_status or marking intake_data_validated. Final submit still
triggers the full validation + worker dispatch flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New page: /portal/dashboard/ — customer can view all orders
- Auth: cookie-based login, shows auth modal if not logged in
- Orders grouped by batch, filtered by DOT/FCC tabs
- Shows service name, amount, discount, status badge, payment method
- Portal API: /api/v1/portal/me now returns compliance_orders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PayPal capture was defaulting to canada_crtc_orders table for all
non-formation orders. Now properly routes compliance_batch orders
to compliance_orders table with batch_id lookup. Also infers
order type from ID prefix (CB-=batch, CO-=compliance, FO-=formation).
MCS-150 form generator: produces DOCX with fax cover sheet + filled
MCS-150 form for faxing to FMCSA at 202-366-3477.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Don't send users to FMCSA portal or state agency sites — keep them
on our site to order services through us. Removed all action_url
from API responses and "Fix this" / "Learn more" links from frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reads dot_number or frn from intake_data and includes in the
notification. DOT orders show DOT#, FCC orders show FRN.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was showing service_fee_cents ($69) instead of actual charge ($35.54).
Now subtracts discount_cents and adds surcharge_cents. Also shows
discount line in notification when a promo code was used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-discountable services: BOC-3 ($25 vendor), D&A (~$100 provider),
MC Authority ($300 gov fee). All other DOT services are pure labor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FMCSA census carrier_operation is single-letter: A=Interstate,
B=Intrastate Hazmat, C=Intrastate Non-Hazmat. Previous code searched
for "interstate" in text which never matched. Now 22,089 interstate
carriers will be properly flagged for IRP/IFTA.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Footer subscribe modal: new "I'm interested in" dropdown with 3 options
- Hoisted JS: reads interest field, validates selection, passes to API
- Subscribe API: routes to different Listmonk lists by interest
(telecom→list 3, trucking→list 8, formation→list 9)
- Interest stored as subscriber attribute for campaign segmentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Order page: insurance referral checkbox (pre-checked) shown when
?ins=1 from checker or carrier has insurance gap. Flag stored
in intake_data.insurance_referral_requested.
- Checker CTA passes &ins=1 when insurance issues found.
- MCS-150: use mcs150Outdated=N from FMCSA API to show green even
without exact date. Fixes "Filing date not available" for carriers
not in local census.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>