- cms855_pdf_filler.py: fills official CMS-855I/B/O/A AcroForms from intake
(name, NPI, DOB, cert-page printed name) and records the signature anchor at
the form's official /Sig box so the e-sign stamper lands on the cert line.
- npi_provider handlers (revalidation/reactivation/enrollment) now generate the
paper CMS-855, upload it to MinIO, request_esign with anchors, and email the
signing link. Human completes/verifies + USPS Priority Mails to the MAC.
- scripts/Dockerfile: copy the official CMS-855I/B/O/A forms into the image.
- order-confirmation email presents both filing methods: paper CMS-855 (no
account needed, client e-signs one page, we print+mail to their MAC) and
I&A surrogacy (faster, needs CMS account). NPPES-only services note that
surrogacy is required (web-only).
- npi_provider handlers record the access model per service in admin todos.
- marketing copy leads with the lowest-friction paper option.
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).
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.
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.
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.
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.
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.
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).
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.
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.
- 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().
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).
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.
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.
- 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.
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.
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.
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
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)
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.
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.