Commit graph

458 commits

Author SHA1 Message Date
justin
157c7a2571 test(npi): add slug consistency check across all wiring places 2026-06-05 01:35:04 -05:00
justin
4b0155542e feat(npi): healthcare marketing pages, nav dropdown, NPI lookup API + free tool + companion data migration/loader 2026-06-05 01:33:36 -05:00
justin
f349d519c6 feat(npi): add NpiIntakeStep wizard + 6 healthcare order pages 2026-06-05 01:26:58 -05:00
justin
e67db156e8 feat(npi): wire 6 healthcare services into catalog, intake, items, handlers, portal 2026-06-05 01:25:05 -05:00
justin
8748c0a141 docs: NPI/healthcare products implementation plan 2026-06-05 01:21:58 -05:00
justin
73e09b12a0 feat: NPI outreach list pipeline (120k cold-emailable + 236k DirectTrust-later) + doc 2026-06-05 01:08:26 -05:00
justin
091ebbd7f9 docs: verified free NPI email-append paths (NPPES endpoint file + free SMTP/MX verify) 2026-06-05 01:00:54 -05:00
justin
604ad151c7 docs: add verified NPI services/pricing + companion-db analysis (217k overdue revalidations) 2026-06-05 00:57:09 -05:00
justin
2db2106ad5 docs: add postcard print-and-mail vendor pricing (Lob verified, PostGrid/Click2Mail quote-gated) 2026-06-05 00:38:41 -05:00
justin
5e4e73674a docs: verify NPPES + EPA RCRA field schemas against live files 2026-06-05 00:34:56 -05:00
justin
70d05e0607 docs: new compliance sectors (NPPES/FMC/EPA) + contact channels beyond postal mail 2026-06-05 00:25:51 -05:00
justin
8400e27d12 Add DOT check CTA to trucking deficiency emails 2026-06-04 18:29:01 -05:00
justin
327c4c9790 Use DOT checker hero on trucking order pages 2026-06-04 13:32:17 -05:00
justin
fcb56a4707 Simplify paid intake review flow 2026-06-04 13:05:21 -05:00
justin
74acda7171 Add trucking order page trust header 2026-06-04 13:01:56 -05:00
justin
40d5643116 Fill trucking campaign quotas with sendable subscribers 2026-06-04 12:36:22 -05:00
justin
e5e70b744b Guard bounce watcher against empty queue IDs 2026-06-04 12:33:53 -05:00
justin
c027d49f43 Fix trucking campaign cron send date 2026-06-04 03:19:35 -05:00
justin
b48fc3a406 Retire burned MTA IPs in warmup script 2026-06-03 23:37:27 -05:00
justin
f42833cc9d Set trucking campaign Reply-To header 2026-06-03 23:35:28 -05:00
justin
ef79e85b41 Align trucking source campaign placeholder list 2026-06-03 23:32:53 -05:00
justin
5c35140a22 Configure trucking deficiency campaign cron env 2026-06-03 23:04:41 -05:00
justin
72da37e47d fix: simplify order price banner copy 2026-06-03 14:02:55 -05:00
justin
965b9ce3c8 fix: add tax deductibility notice to trucking orders 2026-06-03 13:48:37 -05:00
justin
ee07064b0f fix: show trucking order prices and payment options 2026-06-03 13:24:40 -05:00
justin
d7de818f39 fix: stagger trucking campaign catchups and subscriber reattach 2026-06-03 13:21:16 -05:00
justin
6d4c323ab6 feat: daily intake-reminder worker for paid orders with incomplete intake
Adds a systemd-timed worker that nudges customers who paid but never completed
their intake form (which stalls fulfillment).

- migration 087: intake_reminder_count + intake_reminder_last_at on
  compliance_orders (makes the daily run idempotent and bounded), plus a
  partial index for the paid-order eligibility scan.
- scripts/workers/intake_reminder.py: each run emails any paid order with
  intake_data_validated != TRUE, capped at 10 reminders/order, at most one
  consolidated email per customer per day (groups a customer's incomplete
  services into one email). Reuses the post-payment intake URL format
  (/order/{slug}?order={n}) and the API's email validation, skipping
  placeholder/invalid addresses (synthetic@, pipeline.com, etc.). Sends via
  smtplib with SMTP_PASS (verified working in the worker container).
- worker-crons: pw-intake-reminder timer, daily ~noon ET (16:00 UTC).
2026-06-03 00:20:37 -05:00
justin
00c960f5b5 build: pin payments to version-15 + stage apps in deploy.sh erpnext
Two build fixes surfaced while shipping the set-password rename:

1. erpnext/Dockerfile cloned frappe/payments unpinned; its default branch now
   requires Python >=3.14 while frappe/erpnext:v15 ships 3.11, so the image
   build failed with 'Package payments requires a different Python'. Pin the
   clone to --branch version-15.

2. deploy.sh built the erpnext image without first staging the custom Frappe
   apps into the build context (erpnext/build.sh). That meant a baked-code
   change could silently ship stale code. Stage apps when erpnext is built.
2026-06-02 23:13:01 -05:00
justin
5c1341e6a1 portal: fix dead set-password link (rename controller to underscore)
Root cause of the 'Link invalid' onboarding link: Frappe's TemplatePage
resolves a www page's Python controller by converting hyphens to underscores
(see frappe/website/page_renderers/template_page.py set_pymodule: it looks for
'set_password.py' next to 'set-password.html'). Our controller was named
'set-password.py' (hyphen), so os.path.exists() missed it, pymodule_name stayed
None, get_context never ran over HTTP, and the template rendered with no
context -> raw {{ email }}, title 'Link invalid', token never verified. (It
worked under bench/in-process only because we called get_context directly.)

Fix: rename www/set-password.py -> www/set_password.py (route stays
/set-password, driven by the .html filename) and update the whitelisted submit
endpoint path in set-password.html to ...www.set_password.submit.

NOTE: the sibling legacy CRTC/CDR admin pages (admin-filings.py,
admin-resellers.py, cdr-*.py) have the same latent hyphen bug; left as-is since
they're outside the compliance portal, but they are silently controller-less.
2026-06-02 23:10:36 -05:00
justin
668fc6783b compose: give ERPNext CUSTOMER_JWT_SECRET + DATABASE_URL (fix portal drift)
The erpnext service was missing both env vars that the portal needs:
- CUSTOMER_JWT_SECRET: verifies /set-password magic-link tokens signed by the
  API. Without it, the set-password page resolved an empty/placeholder secret
  and showed 'Link invalid' for every customer onboarding link.
- DATABASE_URL: lets www/orders.py read compliance_orders from Postgres for the
  portal's Compliance section.

Both were present on api/workers but never wired to erpnext -> drift. Now the
single ERPNext portal can actually verify invites and show compliance orders.
2026-06-02 23:02:58 -05:00
justin
f6419759e6 portal: converge all compliance orders on the single ERPNext portal
Root cause of customers being unable to log in: ERPNext (portal.performancewest.net)
is the intended single portal and already surfaces compliance/trucking orders
(performancewest_erpnext/www/orders.py reads compliance_orders by email). But
only the Stripe checkout path provisioned the ERPNext Website User up-front
(findOrCreateCustomer). PayPal / crypto / remediation-pipeline orders go straight
to handlePaymentComplete, which created NO portal user and never set
portal_user_created -> no login + no set-password invite (exactly what happened
to the Paul Wilson / Compound Technologies PayPal order).

- handlePaymentComplete: add ensureCompliancePortalUser() in the shared
  post-payment path so EVERY paid compliance order (any payment method) gets an
  ERPNext portal account + the set-password invite. Idempotent.
- Guard against placeholder emails (synthetic@/pipeline.com etc): skip portal
  provisioning and the set-password invite for non-deliverable addresses.
- compliance-orders API: validate email format AND reject placeholder addresses
  at order creation (was: presence-only, so synthetic@pipeline.com passed).
- delivery_worker: never email a set-password invite to a placeholder address.

Note: the legacy PG-customers login (api/routes/portal-auth.ts, /account/*) is
CRTC/formation-era and only backfills canada_crtc_orders/orders, never
compliance_orders. ERPNext is now the consistent portal for compliance.
2026-06-02 22:44:34 -05:00
justin
2b13c36c93 ansible: sync portal nginx template with live working config
The pw-portal-tls.conf.j2 template was stale (basic 47-line version) while the
live /etc/nginx/sites-enabled/pw-portal.conf was hand-maintained with branding,
/assets/ and /files/ serving. A future ansible run would have clobbered the
working config. Sync the template to the live config (templatized) and document
why /files/ must be served from /opt/erpnext-assets, not the docker volume.
2026-06-02 22:20:08 -05:00
justin
dcea3c29bb portal: serve /files/ (logo) from stable host path, fix recurring 403
nginx served /files/ via alias straight into /var/lib/docker/volumes/... but
/var/lib/docker is root 0700 (no traverse for www-data) and docker resets that
perm on restart -> recurring 403 on /files/pw-logo.png (broken portal logo).

Sync the site's public /files/ into /opt/erpnext-assets/assets/files (already
www-data-owned, nginx-traversable, never touched by docker) during asset
extraction, and verify the logo is present. nginx /files/ alias must point here
(separate nginx change applied on server).
2026-06-02 22:18:30 -05:00
justin
c5e6bdbe6d deploy: fix recurring portal CSS breakage from ERPNext asset hash drift
The portal serves Frappe assets from a host copy (/opt/erpnext-assets). Frappe
emits content-hashed filenames that change on every ERPNext rebuild/migrate; the
host copy was never re-synced by deploy.sh, so the manifest referenced hashes
that 404'd on the host -> portal rendered with no CSS (recurring issue).

- Commit extract-erpnext-assets.sh (was untracked, prod-only). It now also runs
  bench build to keep assets.json consistent with dist/, copies the manifest,
  and verifies the login bundle exists on the host before finishing.
- deploy.sh: add an 'erpnext' target that rebuilds, runs bench migrate, and
  re-extracts assets. Plus a cheap drift guard on EVERY deploy that auto-heals
  by re-extracting if the portal manifest references a missing CSS bundle.
2026-06-02 22:12:33 -05:00
justin
f21f3d41d9 DOT D&A binder: remove Performance West and National Drug Screening from vendor directory
Drop the two self-listings (Performance West C-TPA and supervisor training) and
the National Drug Screening / NDS entries from the Suggested Vendors panels.
The non-endorsement disclaimer (which still names Performance West) is unchanged.
2026-06-02 22:02:07 -05:00
justin
843a5bfacb DOT D&A binder: add DER Quick-Start checklist, two-column vendor directory, flow sections
- Add a one-page 'DER Quick-Start Checklist' tear-off as the first content page
  (set-up-once / every-hire / ongoing checkboxes, each pointing to the relevant
  section or form).
- Add a two-column 'Suggested Vendors & Resources' directory page: C-TPA/
  consortium, collection sites & labs, MRO, SAP, supervisor training, and (mode-
  aware) FMCSA Clearinghouse or DOT resources, plus employee help lines. Marked
  as examples not endorsements; mode-aware.
- Remove forced page breaks between consecutive content sections (now a light
  section rule) so they flow continuously; page breaks kept only for the cover,
  quick-start, TOC, each form, the vendor page, regulations, and the addendum.
- New builder helpers: section_rule(), checkbox(), two_col_panels().
2026-06-02 21:54:06 -05:00
justin
501e417584 DOT D&A binder: add Section 11 — Practical Guidance for the Administrator
Adds the real-world know-how a first-time DER needs beyond the bare regs:
- Owner-operators / one-driver companies (must use a consortium; cannot self-test
  or self-select) — the most misunderstood case.
- Audits & penalties: what the new-entrant safety audit asks for and the
  consequences of no program (civil penalties, failed audit, out-of-service).
- Problem test results: dilute, shy bladder, cancelled test, split-specimen.
- Prescriptions / marijuana / CBD (marijuana prohibited regardless of state law;
  CBD trap; route medical questions to the MRO).
- What counts as a refusal (treated as a positive).
- Costs & timeline expectations.
- DER do's and don'ts (act same-day, keep records separate/confidential, never
  tip off a random selection, don't interpret results yourself).
2026-06-02 21:42:55 -05:00
justin
b569a32f3a DOT D&A binder: add driver enrollment guidance + Form G roster
Customers (the DER) had no concrete how-to for onboarding/enrolling drivers or
what information to collect. Add:
- Section 1 'Enrolling a driver (new-hire onboarding)' subsection: exact info to
  collect, the onboarding sequence (collect info, sign Forms A/B, Clearinghouse
  query, prior-employer inquiry, add to C-TPA pool, pre-employment test, wait for
  MRO negative), and a driver-removal note.
- Form G — Driver Enrollment & Covered-Employee Roster: per-driver enrollment
  block (name, DOB, SSN last4, CDL #/state, contact, hire date, test result,
  Clearinghouse/prior-employer status) plus a roster table for the covered pool.
- TOC, email, and handler text updated A-F -> A-G.
2026-06-02 21:39:50 -05:00
justin
7c79cc9a08 DOT D&A delivery email: point CTA + body to the customer portal
The instant-delivery email told customers to 'just reply to this email' with no
way to view/manage their order. Add a portal line in the body and change the CTA
to 'View in Portal' pointing at PORTAL_URL (portal.performancewest.net), matching
delivery_worker/renewal_worker conventions. Add _site_url()/_portal_url() helpers.
2026-06-02 21:37:01 -05:00
justin
9718ab9ffa DOT D&A binder: editable DOCX output, all 6 forms each full-page, service-aware delivery email
- Rewrite dot_da_binder_generator.py to emit an editable .docx (was reportlab PDF)
  so carriers/counsel can review and adapt the program. ~4000 words, 10 sections.
- Render all six required forms (A-F); previously only A, D, E existed. Each form
  starts on its own page (page break) and fills a page.
- Mode-aware policy text for FMCSA/FRA/PHMSA/FTA/FAA/USCG with correct CFR parts
  and random-testing rates; optional single-state Drug-Free Workplace addendum
  (federal DOT program is nationwide; only the optional DFWP addendum is state-keyed).
- Handler now outputs .docx instead of .pdf.
- job_server instant-delivery: attach DOCX (correct MIME) as well as PDF, and use
  DOT-specific email copy + CTA instead of the FCC/telecom boilerplate.
2026-06-02 21:27:44 -05:00
justin
06e59965cc DOT D&A: instant PDF compliance-program binder (49)
Turn the DOT Drug & Alcohol Compliance Program into an automated
instant-delivery deliverable: when a carrier orders, we generate a
complete, print-ready PDF binder and email it (no admin step).

The binder (dot_da_binder_generator.py) bundles everything a small
carrier needs under 49 CFR Part 382 + Part 40:
  - How to manage the program (DER setup + annual operations)
  - Written drug & alcohol testing policy for employees
  - The six DOT test scenarios + triggers
  - Random testing / consortium (C-TPA) instructions
  - Supervisor reasonable-suspicion training + live/online access
  - Violations, SAP access, return-to-duty / follow-up
  - EAP / rehab / treatment resources (SAMHSA, 988, locator, ODAPC)
  - Recordkeeping retention schedule
  - Ready-to-use forms (acknowledgment, reasonable-suspicion,
    post-accident decision worksheet)
  - Regulation citations
  - Optional state Drug-Free Workplace addendum

Policy-variant selection: FMCSA (Part 382) is the trucking default;
honors an explicit dot_da_mode override for FRA/PHMSA/FTA/FAA/USCG.

New DrugAlcoholProgramHandler returns the binder PDF; slug added to
INSTANT_DELIVERY_SLUGS so job_server emails it automatically. Slug
rerouted from MCS150UpdateHandler (was admin-assisted enrollment) and
re-priced as a discountable own-deliverable (no passthrough cost).

Tests: scripts/tests/test_dot_da_binder.py (FMCSA sections, PHMSA+state
addendum, all-modes render) — passing.
2026-06-02 19:28:58 -05:00
justin
058d7cfbfe docs: trucking state-campaign send runbook
Ties the prioritized marketing-send plan (NY HUT -> CT HUF -> D&A -> New
Carrier Startup -> CA MCP) to the existing Listmonk builders
(setup_trucking_campaigns.py creates the drafts + tests;
populate_new_carrier_startup_campaign.py builds the data-targeted New
Carrier audience). Draft/populate/test steps are safe and idempotent;
starting a bulk send is flagged as an irreversible operator-only step.
Cross-references the new fulfillment_status machine and the authorization
e-sign so campaign expectations match fulfillment.
2026-06-02 16:51:08 -05:00
justin
29ad0908ee trucking: pass-through fee disclosure + state fulfillment status machine
Item 2 of the trucking state-authorization plan.

- compliance-orders.ts: populate gov_fee_label for every state-trucking
  service so the variable, billed-at-cost government charges (apportioned
  IRP, IFTA decals, NY HUT, CT HUF, weight-distance, CA MCP+CARB, OS/OW
  permits, bundle) are disclosed at checkout. price_cents stays the flat
  service fee; gov fees pass through at cost.
- migration 086: compliance_orders.fulfillment_status state machine
  (authorization_required -> authorization_signed -> awaiting_customer_
  delegation -> awaiting_secure_credentials -> awaiting_government_fee_
  approval -> awaiting_insurance_filing -> ready_to_file ->
  filed_waiting_state -> completed) + fulfillment_status_at
- state_trucking.py: FULFILLMENT_* constants + _set_fulfillment_status();
  gate sets authorization_required on pause, authorization_signed on
  resume, ready_to_file once the filing todo is queued
- TruckingValueNotice.astro: 'What's included & what's billed at cost'
  disclosure with the authorization/delegation explanation
2026-06-02 16:49:31 -05:00
justin
7ed06780bb trucking: stamp e-signature exactly on form signature lines + state authorization gate
Capture-to-form signature placement so the customer's drawn or typed
signature lands right on the signature rule of the actual form, not in a
sidecar page.

- migration 085: esign_records.signature_anchors (JSONB exact PDF coords,
  lower-left origin, points) + signed_document_minio_key
- signature_stamper.py: signature_box() anchors; anchors_from_acroform()
  pulls the signature field /Rect from a real AcroForm (e.g. MCS-150
  certifySignature); stamp_signature() overlays PNG (auto-trimmed so ink
  rests on the rule) or typed name, scaled to actual page size
- state_trucking_authorization.py: renders the Limited Authorization to
  File PDF and returns (pdf_bytes, anchors)
- esign_stamp.py: stamp_esign_document() downloads unsigned PDF, stamps,
  uploads _signed.pdf, sets signed_document_minio_key (idempotent)
- dot_esign.py: extract certifySignature anchor for MCS-150/closeout forms
  so the federal perjury cert is signed on the line
- state_trucking.py: authorization gate — first run emails signing link
  and PAUSES; resumes with client_approved after signing
- job_server handle_esign_completed: stamp then re-dispatch
- tests: test_signature_placement.py (custom form), and
  test_mcs150_signature_placement.py (official AcroForm) both assert the
  signature lands inside the recorded signature box (verified visually)
2026-06-02 16:44:19 -05:00
justin
345979ed00 Allow multiple referral codes per sales agent
Drop the UNIQUE constraint on sales_agents.email (migration 084) so a single
agent (person/company) can hold several referral codes, each with its own
client discount and commission split. All commission lookups already key on
the unique agent_code, so no lookup logic changes.

Agent-creation endpoint now:
- accepts repeat emails (creates an additional code instead of 409)
- accepts client_discount_value, commission_type, commission_pct per code
- reports existing codes for the email in the response

Both Jay Kordic codes (REF-JKORDIC 7%/12%, REF-JAYK05 5%/15%) now share his
real email jay_kordic@thehorizongroup.biz.
2026-06-02 14:44:22 -05:00
justin
53857574d3 Add referral/discount code to FCC carrier page + REF-JAYK05 agent
Frontend (order/fcc-carrier-registration):
- Add a referral/discount code box on the review step that validates
  against /api/v1/discount/:code and shows the discount line + adjusted
  total. Discount applies to service fee + add-ons, never state filing fees.
- Prefill + auto-apply from ?code= / ?ref= query param (referral links).

Backend (fcc-carrier-registration route):
- Accept discount_code, validate it, store discount_code/discount_cents,
  and subtract from the total. Checkout already reads discount_cents to
  apply the Stripe coupon.
- Create a pending commission when the code belongs to an active sales agent.

Commission fix (agents.createCommission):
- Percent-type agents now earn commission_pct on ALL order types. Previously
  canada_crtc/formation/bundle used flat defaults and ignored percent agents.

Agent: created sales agent Jay Kordic (The Horizon Group) with custom code
REF-JAYK05 -> client gets 5% off discountable services, agent earns 15%.
Idempotent setup script in scripts/create_agent_jaykordic.cjs.
2026-06-02 14:31:22 -05:00
justin
1584a6692b Pivot CRTC offering on FCC carrier page for A-Z wholesale carriers
The international (Q6) CRTC block was framed as a generic upsell (Expand
2026-06-02 13:26:39 -05:00
justin
c9d76545b3 intake: guard hoisted ResellerCertStep script 2026-06-02 13:21:27 -05:00
justin
539fad1396 intake: guard hoisted history/revenue step scripts 2026-06-02 13:19:48 -05:00
justin
bb3604c97b intake: guard category/officer/jurisdiction step scripts when hoisted 2026-06-02 13:16:48 -05:00