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.
This commit is contained in:
justin 2026-06-02 19:28:58 -05:00
parent 058d7cfbfe
commit 06e59965cc
6 changed files with 1172 additions and 2 deletions

View file

@ -0,0 +1,872 @@
"""
DOT Drug & Alcohol Compliance Program binder generator.
Produces the instant-delivery PDF "binder" that ships when a customer buys
the $149 DOT Drug & Alcohol Compliance Program. The binder is a single,
print-ready PDF that bundles everything a small motor carrier needs to run a
compliant program under the applicable DOT testing regulation:
1. How to manage your program (step-by-step instructions)
2. Written drug & alcohol testing policy for employees (mode-specific)
3. Supervisor reasonable-suspicion training materials + live/online access
4. Employee Assistance Program (EAP) + rehab/treatment resources
5. Substance Abuse Professional (SAP) access for DOT violations
6. Copies / citations of the applicable regulations
7. Random testing instructions (consortium / pool management)
8. All required compliance forms (chain-of-custody, consent, refusal, etc.)
9. Recordkeeping instructions + retention schedule
DOT operating administrations (policy variants):
- FMCSA : 49 CFR Part 382 (motor carriers / CDL drivers) <- default
- FRA : 49 CFR Part 219 (railroad / MOW)
- PHMSA : 49 CFR Part 199 (pipeline)
- FTA : 49 CFR Part 655 (transit)
- FAA : 14 CFR Part 120 (aviation)
- USCG : 46 CFR Part 16 (maritime)
Plus an optional state Drug-Free Workplace Program addendum.
For a trucking carrier the program is almost always FMCSA (Part 382). The
``mode`` argument selects the variant; ``state_dfwp`` appends a state
Drug-Free Workplace addendum.
Usage:
from scripts.document_gen.templates.dot_da_binder_generator import (
generate_da_binder,
)
pdf_path = generate_da_binder(
output_path="/tmp/da_binder.pdf",
carrier_name="Acme Trucking LLC",
dot_number="1234567",
mode="fmcsa",
cdl_drivers=4,
der_name="Jane Owner",
der_title="Owner / Designated Employer Representative",
provider_name="Performance West Consortium",
)
"""
from __future__ import annotations
import logging
from datetime import datetime
from pathlib import Path
from typing import Optional
LOG = logging.getLogger("document_gen.da_binder")
# ── Mode metadata ──────────────────────────────────────────────────────────
# Each DOT operating administration has its own testing rule. The binder text
# is written for the chosen mode; FMCSA is the trucking default.
MODE_META: dict[str, dict[str, str]] = {
"fmcsa": {
"agency": "Federal Motor Carrier Safety Administration (FMCSA)",
"part": "49 CFR Part 382",
"procedures": "49 CFR Part 40",
"covered": "safety-sensitive employees who operate a commercial motor "
"vehicle (CMV) requiring a commercial driver's license (CDL)",
"function": "operating a commercial motor vehicle requiring a CDL",
"clearinghouse": True,
},
"fra": {
"agency": "Federal Railroad Administration (FRA)",
"part": "49 CFR Part 219",
"procedures": "49 CFR Part 40",
"covered": "employees who perform covered service (train and engine, "
"signal, dispatching, and maintenance-of-way functions)",
"function": "performing FRA-covered service",
"clearinghouse": False,
},
"phmsa": {
"agency": "Pipeline and Hazardous Materials Safety Administration (PHMSA)",
"part": "49 CFR Part 199",
"procedures": "49 CFR Part 40",
"covered": "employees who perform operation, maintenance, or emergency-"
"response functions on a pipeline or LNG facility",
"function": "performing PHMSA-covered pipeline functions",
"clearinghouse": False,
},
"fta": {
"agency": "Federal Transit Administration (FTA)",
"part": "49 CFR Part 655",
"procedures": "49 CFR Part 40",
"covered": "employees who perform safety-sensitive functions for a "
"recipient of FTA funding",
"function": "performing FTA safety-sensitive functions",
"clearinghouse": False,
},
"faa": {
"agency": "Federal Aviation Administration (FAA)",
"part": "14 CFR Part 120",
"procedures": "49 CFR Part 40",
"covered": "employees who perform safety-sensitive aviation functions",
"function": "performing FAA safety-sensitive functions",
"clearinghouse": False,
},
"uscg": {
"agency": "United States Coast Guard (USCG)",
"part": "46 CFR Part 16",
"procedures": "49 CFR Part 40",
"covered": "crewmembers who occupy or fill a position that affects the "
"safe operation of a vessel",
"function": "occupying a safety-sensitive crew position",
"clearinghouse": False,
},
}
# The five DOT test types & six prohibited-conduct categories are shared by all
# modes (Part 40 + the mode rule). Random rates differ by mode/year; FMCSA 2024+
# is 50% drug / 10% alcohol of the average number of covered positions.
_RANDOM_RATES = {
"fmcsa": "50% (controlled substances) and 10% (alcohol)",
"fra": "the FRA-published annual minimum random testing rates",
"phmsa": "50% (controlled substances) of covered employees",
"fta": "50% (controlled substances) and 10% (alcohol)",
"faa": "the FAA-published annual minimum random testing rates",
"uscg": "the USCG annual minimum random testing rate (currently 25%)",
}
def generate_da_binder(
*,
output_path: str,
carrier_name: str,
dot_number: str = "",
mode: str = "fmcsa",
cdl_drivers: int | str = "",
owner_operators: int | str = "",
der_name: str = "",
der_title: str = "Designated Employer Representative (DER)",
provider_name: str = "Performance West Consortium / C-TPA",
state_dfwp: str = "",
) -> str | None:
"""Render the D&A compliance binder PDF. Returns the path on success."""
try:
from reportlab.lib.colors import HexColor
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (
BaseDocTemplate,
Frame,
ListFlowable,
ListItem,
PageBreak,
PageTemplate,
Paragraph,
Spacer,
Table,
TableStyle,
)
except ImportError:
LOG.warning("reportlab not installed — D&A binder generation unavailable")
return None
mode = (mode or "fmcsa").lower().strip()
meta = MODE_META.get(mode, MODE_META["fmcsa"])
random_rate = _RANDOM_RATES.get(mode, _RANDOM_RATES["fmcsa"])
today = datetime.now().strftime("%B %d, %Y")
NAVY = HexColor("#0b1f3a")
BLUE = HexColor("#1d4ed8")
SLATE = HexColor("#475569")
LIGHT = HexColor("#eef2f7")
styles = getSampleStyleSheet()
h1 = ParagraphStyle(
"H1", parent=styles["Heading1"], fontName="Helvetica-Bold",
fontSize=18, textColor=NAVY, spaceBefore=4, spaceAfter=10, leading=22,
)
h2 = ParagraphStyle(
"H2", parent=styles["Heading2"], fontName="Helvetica-Bold",
fontSize=13, textColor=NAVY, spaceBefore=14, spaceAfter=6, leading=16,
)
h3 = ParagraphStyle(
"H3", parent=styles["Heading3"], fontName="Helvetica-Bold",
fontSize=11, textColor=BLUE, spaceBefore=10, spaceAfter=4, leading=14,
)
body = ParagraphStyle(
"Body", parent=styles["BodyText"], fontName="Helvetica",
fontSize=9.5, leading=14, spaceAfter=6, textColor=HexColor("#1f2937"),
)
body_i = ParagraphStyle("BodyI", parent=body, fontName="Helvetica-Oblique")
small = ParagraphStyle(
"Small", parent=body, fontSize=8, textColor=SLATE, leading=11,
)
cover_title = ParagraphStyle(
"CoverTitle", parent=h1, fontSize=26, alignment=TA_CENTER, leading=30,
)
cover_sub = ParagraphStyle(
"CoverSub", parent=body, fontSize=13, alignment=TA_CENTER,
textColor=SLATE, leading=18,
)
def bullets(items: list[str], style=body) -> ListFlowable:
return ListFlowable(
[ListItem(Paragraph(t, style), leftIndent=10) for t in items],
bulletType="bullet", bulletColor=BLUE, leftIndent=14,
bulletFontSize=7, spaceAfter=4,
)
def numbered(items: list[str], style=body) -> ListFlowable:
return ListFlowable(
[ListItem(Paragraph(t, style)) for t in items],
bulletType="1", leftIndent=18, spaceAfter=4,
)
story: list = []
# ── Cover ──────────────────────────────────────────────────────────────
story.append(Spacer(1, 1.4 * inch))
story.append(Paragraph("DOT Drug &amp; Alcohol", cover_title))
story.append(Paragraph("Compliance Program Binder", cover_title))
story.append(Spacer(1, 0.25 * inch))
story.append(Paragraph(
f"Prepared under {meta['part']} and the U.S. DOT procedures at "
f"{meta['procedures']}", cover_sub))
story.append(Spacer(1, 0.55 * inch))
cover_rows = [
["Motor Carrier", carrier_name or ""],
["USDOT Number", str(dot_number) or ""],
["Regulating Agency", meta["agency"]],
["Covered Employees", _covered_count(cdl_drivers, owner_operators)],
["Designated Employer Rep.", f"{der_name or ''}" +
(f"{der_title}" if der_name else "")],
["Testing Program / C-TPA", provider_name],
["Program Effective Date", today],
]
t = Table(cover_rows, colWidths=[2.1 * inch, 3.9 * inch])
t.setStyle(TableStyle([
("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
("FONTNAME", (1, 0), (1, -1), "Helvetica"),
("FONTSIZE", (0, 0), (-1, -1), 10),
("TEXTCOLOR", (0, 0), (0, -1), NAVY),
("TEXTCOLOR", (1, 0), (1, -1), HexColor("#1f2937")),
("BACKGROUND", (0, 0), (0, -1), LIGHT),
("LINEBELOW", (0, 0), (-1, -1), 0.5, HexColor("#cbd5e1")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 7),
("BOTTOMPADDING", (0, 0), (-1, -1), 7),
("LEFTPADDING", (0, 0), (-1, -1), 10),
]))
story.append(t)
story.append(Spacer(1, 0.5 * inch))
story.append(Paragraph(
"Prepared by Performance West Inc. This binder is a compliance "
"resource, not legal advice. The motor carrier remains responsible "
"for implementing and maintaining its program.", small))
story.append(PageBreak())
# ── Table of contents ──────────────────────────────────────────────────
story.append(Paragraph("What's Inside This Binder", h1))
toc = [
"<b>Section 1 — How to Manage Your Program.</b> Step-by-step setup and "
"ongoing operating instructions for the Designated Employer "
"Representative (DER).",
"<b>Section 2 — Written Testing Policy.</b> Your company drug &amp; "
"alcohol testing policy to distribute to every covered employee.",
"<b>Section 3 — When Testing Is Required.</b> The six DOT test "
"scenarios and what triggers each one.",
"<b>Section 4 — Random Testing Program.</b> How the consortium random "
"pool works and your obligations.",
"<b>Section 5 — Supervisor Training.</b> Reasonable-suspicion training "
"materials and how to access live/online training.",
"<b>Section 6 — Violations, SAP &amp; Return-to-Duty.</b> What happens "
"after a positive test, and Substance Abuse Professional access.",
"<b>Section 7 — EAP, Rehab &amp; Treatment Resources.</b> Employee "
"Assistance Program information and treatment referrals.",
"<b>Section 8 — Recordkeeping.</b> What to keep, where, and for how long.",
"<b>Section 9 — Required Forms.</b> Every form you need, ready to use.",
"<b>Section 10 — The Regulations.</b> Citations and how to read the "
"actual rule text.",
]
story.append(bullets(toc))
if state_dfwp:
story.append(Spacer(1, 4))
story.append(Paragraph(
f"<b>Addendum — {state_dfwp} Drug-Free Workplace Program.</b> "
"State-specific policy supplement that runs alongside your DOT "
"program.", body))
story.append(PageBreak())
# ── Section 1 — Manage your program ────────────────────────────────────
story.append(Paragraph("Section 1 — How to Manage Your Program", h1))
story.append(Paragraph(
f"As a motor carrier subject to {meta['part']}, you must operate a "
"drug and alcohol testing program covering every "
f"{meta['covered']}. The person who runs the program day-to-day is the "
"<b>Designated Employer Representative (DER)</b>. Follow these steps.",
body))
story.append(Paragraph("Initial setup (do once)", h3))
story.append(numbered([
"<b>Name your DER.</b> Record the DER's name and contact information "
"(this binder lists them on the cover). The DER receives test results "
"and removes employees from safety-sensitive duty when required.",
"<b>Adopt the written policy</b> in Section 2. Sign it, date it, and "
"keep the signed master copy.",
"<b>Distribute the policy</b> to every covered employee and collect a "
"signed acknowledgment (Form A in Section 9). Give a copy to each new "
"hire before they perform safety-sensitive duties.",
"<b>Join a testing program / consortium (C-TPA).</b> Your program is "
f"administered through {provider_name}, which manages your random "
"pool, scheduling, collection sites, the lab, and the Medical Review "
"Officer (MRO).",
"<b>Conduct pre-employment drug tests</b> with a verified negative "
"result before any new covered employee performs safety-sensitive "
"functions.",
] + ([
"<b>Register with the FMCSA Clearinghouse</b> at "
"clearinghouse.fmcsa.dot.gov and run the required queries (see below)."
] if meta.get("clearinghouse") else [])))
story.append(Paragraph("Ongoing operations (every year)", h3))
ongoing = [
"Conduct <b>random tests</b> throughout the year at the required "
f"minimum annual rate of {random_rate} of covered positions, spread "
"evenly across the year (see Section 4).",
"Test for <b>reasonable suspicion</b> when a trained supervisor "
"observes the signs (see Section 5).",
"Conduct <b>post-accident testing</b> when DOT criteria are met "
"(see Section 3).",
"Run <b>return-to-duty and follow-up tests</b> for any employee who "
"violated the rule and completed the SAP process (see Section 6).",
"Keep all <b>records</b> for the required retention periods "
"(see Section 8).",
]
if meta.get("clearinghouse"):
ongoing.append(
"Run an <b>annual FMCSA Clearinghouse query</b> on every CDL "
"driver, and a <b>full query</b> (with driver consent) before "
"hiring any new driver. Report violations and refusals to the "
"Clearinghouse.")
story.append(bullets(ongoing))
story.append(PageBreak())
# ── Section 2 — Written policy ─────────────────────────────────────────
story.append(Paragraph("Section 2 — Written Drug &amp; Alcohol Testing Policy", h1))
story.append(Paragraph(
f"This is the policy {carrier_name or 'the Company'} adopts and gives "
f"to every covered employee. It satisfies the written-policy "
f"requirement of {meta['part']} and {meta['procedures']}.", body_i))
story.append(Paragraph("1. Purpose and Authority", h3))
story.append(Paragraph(
f"{carrier_name or 'The Company'} (the \"Company\") is committed to a "
"safe, drug- and alcohol-free workplace. This policy implements the "
f"requirements of the {meta['agency']} at {meta['part']} and the U.S. "
f"Department of Transportation testing procedures at {meta['procedures']}. "
"Where this policy conflicts with DOT regulations, the regulations "
"govern.", body))
story.append(Paragraph("2. Who Is Covered", h3))
story.append(Paragraph(
f"This policy applies to every {meta['covered']}. A covered employee "
f"is subject to testing whenever {meta['function']}.", body))
story.append(Paragraph("3. Prohibited Conduct", h3))
story.append(Paragraph("A covered employee must not:", body))
story.append(bullets([
"Report for duty or remain on duty with an alcohol concentration of "
"0.04 or greater.",
"Use alcohol while performing safety-sensitive functions, within "
"4 hours before, or within 8 hours after an accident (or until "
"tested).",
"Use any illegal drug, or use a controlled substance unless under a "
"valid prescription consistent with safe operation.",
"Report for duty or remain on duty when using a controlled substance "
"that the prescribing physician has not authorized as consistent with "
"safe performance.",
"Refuse to submit to a required test (a refusal is treated the same as "
"a verified positive test).",
"Test positive on any DOT-required drug or alcohol test.",
]))
story.append(Paragraph("4. Required Tests", h3))
story.append(Paragraph(
"The Company conducts the following DOT tests: pre-employment, random, "
"reasonable suspicion, post-accident, return-to-duty, and follow-up. "
"Section 3 explains when each applies. Drug tests screen for marijuana, "
"cocaine, opioids, amphetamines, and phencyclidine (PCP) using the "
"DOT 5-panel and are verified by a Medical Review Officer (MRO).", body))
story.append(Paragraph("5. Consequences of a Violation", h3))
story.append(Paragraph(
"An employee who has a verified positive test, an alcohol "
"concentration of 0.04 or greater, or who refuses a test is "
"immediately removed from safety-sensitive duty. The employee may not "
"return until they complete the return-to-duty process with a DOT-"
"qualified Substance Abuse Professional (SAP) and pass a return-to-duty "
"test (see Section 6).", body))
story.append(Paragraph("6. Designated Employer Representative", h3))
story.append(Paragraph(
f"The Company's DER is {der_name or '________________________'}"
f"{(', ' + der_title) if der_name else ''}. The DER receives test "
"results, schedules tests, and removes employees from duty as required. "
"Questions about this policy should be directed to the DER.", body))
story.append(Paragraph("7. Employee Assistance", h3))
story.append(Paragraph(
"Information about substance abuse, its effects, and available "
"treatment and Employee Assistance resources is provided in Section 7 "
"of this binder and is available from the DER.", body))
story.append(PageBreak())
# ── Section 3 — When testing is required ─────────────<E29480><E29480>─────────────────
story.append(Paragraph("Section 3 — When Testing Is Required", h1))
test_table = [
["Test Type", "When It Happens"],
["Pre-employment",
"Before a new covered employee first performs safety-sensitive "
"functions. Drug test with a verified negative result is required."],
["Random",
"Unannounced, spread throughout the year, selected by the consortium "
"from the random pool (see Section 4)."],
["Reasonable suspicion",
"When a trained supervisor observes specific, articulable signs of "
"drug or alcohol use (see Section 5)."],
["Post-accident",
"After a DOT-recordable accident meeting the rule's criteria "
"(fatality; citation + injury requiring medical treatment away from "
"the scene; or citation + a vehicle towed from the scene). Alcohol "
"test within 8 hours, drug test within 32 hours."],
["Return-to-duty",
"Before an employee who violated the rule may return to safety-"
"sensitive duty, after completing the SAP process. Directly observed."],
["Follow-up",
"A SAP-directed schedule of unannounced tests (at least 6 in the "
"first 12 months) after return to duty. Directly observed."],
]
tt = Table(test_table, colWidths=[1.5 * inch, 4.5 * inch])
tt.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#ffffff")),
("BACKGROUND", (0, 0), (-1, 0), NAVY),
("BACKGROUND", (0, 1), (0, -1), LIGHT),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("GRID", (0, 0), (-1, -1), 0.4, HexColor("#cbd5e1")),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 7),
("RIGHTPADDING", (0, 0), (-1, -1), 7),
]))
story.append(tt)
story.append(PageBreak())
# ── Section 4 — Random testing ─────────────────────────────────────────
story.append(Paragraph("Section 4 — Random Testing Program", h1))
story.append(Paragraph(
f"Random testing is the backbone of your program. The minimum annual "
f"random testing rate is {random_rate} of your average number of "
"covered positions. Because most small carriers cannot meet the "
"spread-and-rate requirements on their own, your covered employees are "
f"enrolled in a random pool managed by {provider_name} "
"(a Consortium/Third-Party Administrator, or C-TPA).", body))
story.append(Paragraph("How the random pool works", h3))
story.append(numbered([
"Every covered employee is placed in the consortium's random pool.",
"Each selection period, the consortium randomly selects names using a "
"scientifically valid method.",
"Selected employees are notified and must proceed <b>immediately</b> "
"to the collection site — no advance notice, no delay.",
"Selections are spread reasonably throughout the year so testing is "
"truly unannounced and unpredictable.",
"The consortium tracks the rate and gives you a year-end summary for "
"your records and any DOT audit.",
]))
story.append(Paragraph("Your responsibilities", h3))
story.append(bullets([
"Keep the consortium's roster of covered employees current — add new "
"hires and remove departures promptly.",
"Send selected employees to testing right away and document that they "
"went.",
"Never select or 'volunteer' specific people — selection must stay "
"random.",
"Keep the consortium's selection and results records (see Section 8).",
]))
story.append(PageBreak())
# ── Section 5 — Supervisor training ────────────────────────────────────
story.append(Paragraph("Section 5 — Supervisor Reasonable-Suspicion Training", h1))
story.append(Paragraph(
f"{meta['part']} requires that any supervisor who may make a "
"reasonable-suspicion testing decision complete at least <b>60 minutes "
"of training on the symptoms of alcohol misuse</b> and at least "
"<b>60 minutes on the symptoms of controlled-substance use</b> "
"(120 minutes total). Keep a record of who was trained and when.", body))
story.append(Paragraph("Signs a trained supervisor watches for", h3))
story.append(bullets([
"<b>Appearance:</b> bloodshot or watery eyes, dilated/constricted "
"pupils, flushed face, disheveled clothing, drug paraphernalia.",
"<b>Behavior:</b> mood swings, agitation, euphoria, drowsiness, "
"paranoia, unusual or unsafe risk-taking.",
"<b>Speech:</b> slurred, rapid, incoherent, or unusually talkative.",
"<b>Coordination:</b> unsteady gait, tremors, poor balance, fumbling.",
"<b>Odor:</b> alcohol or marijuana odor on breath, body, or clothing.",
"<b>Performance:</b> sudden decline, missed deadlines, accidents, "
"near-misses, frequent absences.",
]))
story.append(Paragraph("Making a reasonable-suspicion decision", h3))
story.append(numbered([
"Two trained supervisors are not required — one trained supervisor's "
"specific, contemporaneous, articulable observations are enough.",
"Document the observations in writing immediately (Form D in "
"Section 9), signed and dated within 24 hours.",
"Direct the employee to testing and do not let them drive themselves.",
"Remove the employee from safety-sensitive duty pending results.",
]))
story.append(Paragraph("Live / online supervisor training access", h3))
story.append(Paragraph(
"Your program includes access to DOT-compliant supervisor training. "
"To complete the required 120-minute course online (or schedule a live "
"session), contact us and we will send each supervisor an enrollment "
"link and issue a completion certificate for your records:", body))
story.append(bullets([
"<b>Email:</b> compliance@performancewest.com — subject line "
"\"Supervisor Training\" with your USDOT number.",
"<b>What you get:</b> self-paced online modules, a knowledge check, "
"and a dated certificate of completion to keep on file.",
]))
story.append(PageBreak())
# ── Section 6 — Violations / SAP ───────────────────────────────────────
story.append(Paragraph("Section 6 — Violations, SAP &amp; Return-to-Duty", h1))
story.append(Paragraph(
"If a covered employee has a verified positive test, an alcohol "
"concentration of 0.04 or greater, or refuses a test, you must "
"<b>immediately remove them from safety-sensitive duty</b>. They cannot "
"return until they complete the DOT return-to-duty process.", body))
story.append(Paragraph("The return-to-duty process", h3))
story.append(numbered([
"<b>Remove from duty</b> and give the employee the contact information "
"for a DOT-qualified Substance Abuse Professional (SAP).",
"<b>SAP evaluation.</b> The SAP evaluates the employee and prescribes "
"education and/or treatment.",
"<b>Treatment / education</b> is completed as directed by the SAP.",
"<b>Follow-up evaluation.</b> The SAP confirms the employee complied "
"and is eligible to return.",
"<b>Return-to-duty test.</b> The employee must pass a directly "
"observed return-to-duty test before resuming duties.",
"<b>Follow-up testing.</b> The SAP sets an unannounced follow-up "
"testing schedule (at least 6 tests in the first 12 months, for up to "
"5 years).",
]))
story.append(Paragraph("SAP access", h3))
story.append(Paragraph(
"Your program includes access to a network of DOT-qualified Substance "
"Abuse Professionals. To get a SAP referral for an employee, contact "
"the DER or email compliance@performancewest.com with your USDOT "
"number; we will provide qualified SAP contacts in the employee's "
"area. You can also locate a SAP through the DOT and industry "
"directories listed in Section 7.", body))
if meta.get("clearinghouse"):
story.append(Paragraph("FMCSA Clearinghouse reporting", h3))
story.append(Paragraph(
"Report the violation, refusal, the SAP's name, the negative "
"return-to-duty test, and completion of follow-up testing to the "
"FMCSA Clearinghouse within the required timeframes. A driver with "
"an unresolved Clearinghouse violation is 'prohibited' and may not "
"operate a CMV.", body))
story.append(PageBreak())
# ── Section 7 — EAP / rehab ────────────────────────────────────────────
story.append(Paragraph("Section 7 — EAP, Rehab &amp; Treatment Resources", h1))
story.append(Paragraph(
"DOT rules require that you give covered employees educational "
"materials that explain the requirements of the rule and the "
"employer's policies and procedures, plus information on the effects "
"of alcohol and controlled-substance use and available resources. Use "
"the resources below and share them with employees.", body))
story.append(Paragraph("National help lines and directories", h3))
story.append(bullets([
"<b>SAMHSA National Helpline:</b> 1-800-662-HELP (4357) — free, "
"confidential, 24/7 treatment referral and information service.",
"<b>SAMHSA treatment locator:</b> findtreatment.gov — searchable "
"directory of treatment facilities by location.",
"<b>988 Suicide &amp; Crisis Lifeline:</b> call or text 988.",
"<b>Alcoholics Anonymous:</b> aa.org — local meeting finder.",
"<b>Narcotics Anonymous:</b> na.org — local meeting finder.",
"<b>DOT SAP information:</b> transportation.gov/odapc — Office of "
"Drug &amp; Alcohol Policy and Compliance, including the SAP and "
"return-to-duty guidance.",
]))
story.append(Paragraph("Employee Assistance Program (EAP)", h3))
story.append(Paragraph(
"An EAP gives employees confidential access to counseling and referral "
"services. If your company offers an EAP, list its contact "
"information on the acknowledgment you give employees. If you do not "
"have an EAP, the national resources above and a SAP referral satisfy "
"the educational and referral expectations of the rule. We can help "
"you add a low-cost EAP — email compliance@performancewest.com.", body))
story.append(PageBreak())
# ── Section 8 — Recordkeeping ──────────────────────────────────────────
story.append(Paragraph("Section 8 — Recordkeeping", h1))
story.append(Paragraph(
"Keep your records organized and secure; you must produce them in a "
"DOT audit. The DER (or your C-TPA on your behalf) maintains them. "
"Retention periods under Part 40 / the mode rule:", body))
rec_table = [
["Record", "Keep For"],
["Verified positive test results; refusals; SAP reports; "
"return-to-duty and follow-up test records; alcohol tests of 0.02+",
"5 years"],
["Random selection records and the annual random testing rate "
"calculation", "5 years"],
["Reasonable-suspicion and post-accident test records", "5 years"],
["EBT calibration / collection-site documentation", "5 years"],
["Annual MIS summary (if required of your operation)", "5 years"],
["Negative test results and cancelled tests", "1 year"],
["Supervisor training records", "While employed + as required"],
["Employee policy acknowledgments", "Duration of employment + 1 year"],
]
rt = Table(rec_table, colWidths=[4.4 * inch, 1.6 * inch])
rt.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#ffffff")),
("BACKGROUND", (0, 0), (-1, 0), NAVY),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("GRID", (0, 0), (-1, -1), 0.4, HexColor("#cbd5e1")),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#ffffff"), LIGHT]),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 7),
]))
story.append(rt)
story.append(Spacer(1, 8))
story.append(Paragraph(
"Store records so they are confidential and retrievable. Your C-TPA "
"keeps a parallel set of consortium records; request a copy any time.",
small))
story.append(PageBreak())
# ── Section 9 — Forms ──────────────────────────────────────────────────
story.append(Paragraph("Section 9 — Required Compliance Forms", h1))
story.append(Paragraph(
"The forms on the following pages are ready to print and use. The "
"federal chain-of-custody and alcohol-testing forms (the CCF and the "
"DOT Alcohol Testing Form) are provided by the collection site and "
"lab; you do not print those yourself, but your C-TPA supplies them at "
"each collection.", body))
story.append(bullets([
"<b>Form A</b> — Employee Policy Receipt &amp; Acknowledgment.",
"<b>Form B</b> — Pre-Employment Testing Consent &amp; Prior-Employer "
"Inquiry.",
"<b>Form C</b> — Test Notification / Authorization to Test.",
"<b>Form D</b> — Reasonable-Suspicion Observation Record.",
"<b>Form E</b> — Post-Accident Testing Decision Worksheet.",
"<b>Form F</b> — Supervisor Training Completion Record.",
]))
_append_form_a(story, h2, h3, body, small, carrier_name, meta)
_append_form_d(story, h2, body, small, carrier_name)
_append_form_e(story, h2, body, small, carrier_name)
# ── Section 10 — Regulations ───────────────────────────────────────────
story.append(Paragraph("Section 10 — The Regulations", h1))
story.append(Paragraph(
"Your program is governed by two rules that work together: the DOT-"
f"wide testing procedures at {meta['procedures']}, and the mode rule "
f"at {meta['part']} from the {meta['agency']}. Read the official, "
"always-current text on the free government sites below.", body))
story.append(bullets([
f"<b>{meta['procedures']} (DOT testing procedures):</b> "
"ecfr.gov/current/title-49/subtitle-A/part-40",
f"<b>{meta['part']} (your mode rule):</b> ecfr.gov — search the part "
"number; this is the rule that defines who is covered and the testing "
"scenarios.",
"<b>DOT ODAPC (plain-language guidance, forms, brochures):</b> "
"transportation.gov/odapc",
] + ([
"<b>FMCSA Drug &amp; Alcohol Clearinghouse:</b> "
"clearinghouse.fmcsa.dot.gov",
"<b>FMCSA testing overview:</b> fmcsa.dot.gov/regulations/"
"drug-alcohol-testing-program",
] if meta.get("clearinghouse") else [])))
story.append(Spacer(1, 10))
story.append(Paragraph(
"Regulations change. This binder reflects the rules in effect on "
f"{today}. Always confirm the current rule text on ecfr.gov, and "
"contact us with any questions at compliance@performancewest.com.",
small))
# ── Optional state DFWP addendum ───────────────────────────────────────
if state_dfwp:
story.append(PageBreak())
story.append(Paragraph(
f"Addendum — {state_dfwp} Drug-Free Workplace Program", h1))
story.append(Paragraph(
f"In addition to your federal DOT program, "
f"{carrier_name or 'the Company'} maintains a Drug-Free Workplace "
f"Program consistent with {state_dfwp} law. This program runs "
"alongside (and does not "
"replace) the DOT testing program in this binder.", body))
story.append(bullets([
"The Company prohibits the unlawful manufacture, distribution, "
"possession, or use of controlled substances in the workplace.",
"Employees must notify the Company of any criminal drug statute "
"conviction for a violation occurring in the workplace as required "
"by state law.",
"The Company makes a good-faith effort to maintain a drug-free "
"workplace through this policy, the awareness materials in Section "
"7, and available counseling and treatment resources.",
f"State-specific notice, testing, and appeal rights under "
f"{state_dfwp} law apply where they exceed federal requirements; "
"DOT rules always govern DOT-required tests.",
]))
story.append(Spacer(1, 8))
story.append(Paragraph(
f"Confirm your {state_dfwp} program registration, premium-discount, "
"and notice requirements with the state agency or your insurer. We "
"can help — email compliance@performancewest.com.", small))
# ── Footer drawing ─────────────────────────────────────────────────────
def _decorations(canvas, doc):
canvas.saveState()
w, h = letter
# top accent bar (skip cover)
if doc.page > 1:
canvas.setFillColor(NAVY)
canvas.rect(0, h - 0.28 * inch, w, 0.28 * inch, fill=1, stroke=0)
canvas.setFillColor(HexColor("#ffffff"))
canvas.setFont("Helvetica-Bold", 8)
canvas.drawString(0.85 * inch, h - 0.19 * inch,
"DOT Drug & Alcohol Compliance Program")
canvas.drawRightString(w - 0.85 * inch, h - 0.19 * inch,
(carrier_name or "")[:48])
# footer
canvas.setFillColor(SLATE)
canvas.setFont("Helvetica", 7.5)
canvas.drawString(0.85 * inch, 0.42 * inch,
f"Performance West Inc. • {meta['part']}")
canvas.drawRightString(w - 0.85 * inch, 0.42 * inch,
f"Page {doc.page}")
canvas.setStrokeColor(HexColor("#cbd5e1"))
canvas.setLineWidth(0.4)
canvas.line(0.85 * inch, 0.58 * inch, w - 0.85 * inch, 0.58 * inch)
canvas.restoreState()
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
doc = BaseDocTemplate(
str(out), pagesize=letter,
leftMargin=0.85 * inch, rightMargin=0.85 * inch,
topMargin=0.55 * inch, bottomMargin=0.75 * inch,
title="DOT Drug & Alcohol Compliance Program",
author="Performance West Inc.",
)
frame = Frame(
doc.leftMargin, doc.bottomMargin,
doc.width, doc.height - 0.18 * inch, id="body",
)
doc.addPageTemplates([
PageTemplate(id="main", frames=[frame], onPage=_decorations)
])
doc.build(story)
LOG.info("Generated D&A binder (%s mode) -> %s", mode, out)
return str(out)
# ── Helpers ────────────────────────────────────────────────────────────────
def _covered_count(cdl_drivers, owner_operators) -> str:
try:
n = int(str(cdl_drivers).strip() or 0)
except (TypeError, ValueError):
n = 0
try:
oo = int(str(owner_operators).strip() or 0)
except (TypeError, ValueError):
oo = 0
total = n + oo
if total <= 0:
return ""
extra = f" ({oo} owner-operator)" if oo else ""
return f"{total} covered position(s){extra}"
def _append_form_a(story, h2, h3, body, small, carrier_name, meta):
from reportlab.lib.units import inch
from reportlab.platypus import PageBreak, Paragraph, Spacer
story.append(PageBreak())
story.append(Paragraph("Form A — Employee Policy Receipt &amp; Acknowledgment", h2))
story.append(Paragraph(
f"I acknowledge that I have received, read, and understand the "
f"{carrier_name or 'Company'} Drug &amp; Alcohol Testing Policy adopted "
f"under {meta['part']}. I understand that I am subject to pre-"
"employment, random, reasonable-suspicion, post-accident, return-to-"
"duty, and follow-up testing, and that a positive test or refusal will "
"remove me from safety-sensitive duty.", body))
story.append(Spacer(1, 22))
for label in ["Employee name (print): ______________________________________",
"Employee signature: _________________________________________",
"Date: ____________________ Position: ____________________",
"DER / witness: ______________________________________________"]:
story.append(Paragraph(label, body))
story.append(Spacer(1, 10))
story.append(Paragraph(
"Keep the signed original in the employee's file for the duration of "
"employment plus one year.", small))
def _append_form_d(story, h2, body, small, carrier_name):
from reportlab.platypus import PageBreak, Paragraph, Spacer
story.append(PageBreak())
story.append(Paragraph("Form D — Reasonable-Suspicion Observation Record", h2))
story.append(Paragraph(
"Complete immediately when directing an employee to a reasonable-"
"suspicion test. Must be signed and dated within 24 hours.", body))
story.append(Spacer(1, 10))
for label in [
"Employee observed: __________________________________________",
"Date / time of observation: _________________________________",
"Location: ____________________________________________________",
"Specific observations (appearance, behavior, speech, odor, "
"coordination, performance):",
"_____________________________________________________________",
"_____________________________________________________________",
"_____________________________________________________________",
"Test directed: [ ] Drug [ ] Alcohol [ ] Both",
"Trained supervisor (print): _________________________________",
"Supervisor signature: ________________________ Date: _______",
]:
story.append(Paragraph(label, body))
story.append(Spacer(1, 8))
story.append(Paragraph(
"Retain for 5 years. The supervisor must have completed the required "
"120 minutes of reasonable-suspicion training (see Form F).", small))
def _append_form_e(story, h2, body, small, carrier_name):
from reportlab.platypus import PageBreak, Paragraph, Spacer
story.append(PageBreak())
story.append(Paragraph("Form E — Post-Accident Testing Decision Worksheet", h2))
story.append(Paragraph(
"Use immediately after any accident to decide whether DOT post-"
"accident testing is required. Test if ANY box below is checked.", body))
story.append(Spacer(1, 8))
for label in [
"[ ] The accident involved a <b>fatality</b>.",
"[ ] The driver received a <b>citation</b> AND a person was "
"<b>injured and required medical treatment away from the scene</b>.",
"[ ] The driver received a <b>citation</b> AND a <b>vehicle was "
"towed</b> from the scene due to disabling damage.",
]:
story.append(Paragraph(label, body))
story.append(Spacer(1, 4))
story.append(Spacer(1, 8))
story.append(Paragraph(
"If testing is required: alcohol test as soon as possible "
"(within 8 hours) and drug test within 32 hours. Document any reason a "
"required test could not be completed.", body))
story.append(Spacer(1, 12))
for label in [
"Driver: ____________________________ Date/time of accident: ______",
"Decision: [ ] Test required [ ] Not required",
"DER (print): ______________________ Signature: __________________",
]:
story.append(Paragraph(label, body))
story.append(Spacer(1, 10))
story.append(Paragraph("Retain for 5 years.", small))

View file

@ -0,0 +1,117 @@
"""Verify the DOT Drug & Alcohol compliance binder generates correctly.
Renders the binder for the default FMCSA (trucking) variant plus a non-default
DOT mode with a state Drug-Free Workplace addendum, and asserts the PDFs are
multi-page and contain the key deliverable sections the program promises:
written policy, program-management instructions, supervisor training, EAP/SAP
resources, random-testing instructions, recordkeeping, forms, and regulations.
Requires: reportlab, pypdf, and (for text extraction) pdfplumber.
Run from the repo root with a venv that has those installed.
"""
from __future__ import annotations
import importlib.util
import os
import tempfile
_GEN = os.path.abspath(
os.path.join(
os.path.dirname(__file__), "..", "document_gen", "templates",
"dot_da_binder_generator.py",
)
)
_spec = importlib.util.spec_from_file_location("dot_da_binder_generator", _GEN)
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod) # type: ignore[union-attr]
generate_da_binder = _mod.generate_da_binder
MODE_META = _mod.MODE_META
def _extract_text(path: str) -> str:
import pypdf
reader = pypdf.PdfReader(path)
return "\n".join(page.extract_text() or "" for page in reader.pages)
def test_fmcsa_binder_has_all_sections():
with tempfile.TemporaryDirectory() as d:
out = os.path.join(d, "binder.pdf")
path = generate_da_binder(
output_path=out,
carrier_name="Acme Trucking LLC",
dot_number="3456789",
mode="fmcsa",
cdl_drivers=4,
owner_operators=1,
der_name="Jane Owner",
provider_name="Performance West Consortium",
)
assert path and os.path.exists(path)
import pypdf
assert len(pypdf.PdfReader(path).pages) >= 10, "binder should be substantial"
text = _extract_text(path)
# FMCSA-specific facts
assert "49 CFR Part 382" in text
assert "Federal Motor Carrier Safety Administration" in text
assert "Clearinghouse" in text, "FMCSA binder must mention the Clearinghouse"
# The promised deliverables (one assertion each)
for needle in [
"How to Manage Your Program", # program-management instructions
"Written Drug & Alcohol Testing Policy", # written policy
"Random Testing Program", # random testing instructions
"Supervisor", # supervisor training materials
"Substance Abuse Professional", # SAP access
"SAMHSA", # EAP / rehab / treatment resources
"Recordkeeping", # recordkeeping instructions
"Required Compliance Forms", # required forms
"Acknowledgment", # an actual form
"The Regulations", # copies/citations of regulations
]:
assert needle in text, f"missing deliverable section: {needle!r}"
def test_non_fmcsa_mode_and_state_addendum():
with tempfile.TemporaryDirectory() as d:
out = os.path.join(d, "binder_phmsa.pdf")
path = generate_da_binder(
output_path=out,
carrier_name="Pipeline Co",
dot_number="111",
mode="phmsa",
cdl_drivers=3,
state_dfwp="Georgia",
)
assert path and os.path.exists(path)
text = _extract_text(path)
# PHMSA variant swaps in the right rule + agency, no Clearinghouse
assert "49 CFR Part 199" in text
assert "Pipeline and Hazardous Materials Safety Administration" in text
assert "clearinghouse.fmcsa.dot.gov" not in text.lower()
# State addendum present
assert "Georgia Drug-Free Workplace Program" in text
def test_all_modes_render():
with tempfile.TemporaryDirectory() as d:
for mode in MODE_META:
out = os.path.join(d, f"{mode}.pdf")
path = generate_da_binder(
output_path=out,
carrier_name="Test Co",
dot_number="1",
mode=mode,
cdl_drivers=2,
)
assert path and os.path.exists(path), f"mode {mode} failed to render"
if __name__ == "__main__":
test_fmcsa_binder_has_all_sections()
test_non_fmcsa_mode_and_state_addendum()
test_all_modes_render()
print("All DOT D&A binder tests passed.")

View file

@ -1204,6 +1204,8 @@ def handle_process_compliance_service(payload: dict) -> dict:
"policy-development", "ccpa-audit", "privacy-policy",
"data-mapping", "breach-response", "consent-audit",
"dnc-compliance", "campaign-review",
# DOT / FMCSA instant-delivery binders
"dot-drug-alcohol", # generates the D&A compliance program binder
}
if service_slug in INSTANT_DELIVERY_SLUGS and minio_paths:
customer_email = order.get("customer_email")

View file

@ -47,6 +47,7 @@ from .state_puc_filing import StatePucFilingHandler
from .fcc_carrier_registration import FCCCarrierRegistrationHandler
# DOT / FMCSA Motor Carrier Services
from .mcs150_update import MCS150UpdateHandler
from .dot_drug_alcohol import DrugAlcoholProgramHandler
from .boc3_filing import BOC3FilingHandler
# State-level trucking compliance (IRP, IFTA, weight taxes, MCP, etc.)
from .state_trucking import StateTruckingHandler
@ -109,7 +110,7 @@ SERVICE_HANDLERS: dict[str, type] = {
"ucr-registration": MCS150UpdateHandler, # admin-assisted, same pattern
"dot-registration": MCS150UpdateHandler, # admin-assisted
"mc-authority": MCS150UpdateHandler, # admin-assisted
"dot-drug-alcohol": MCS150UpdateHandler, # admin-assisted (partner enrollment)
"dot-drug-alcohol": DrugAlcoholProgramHandler, # instant PDF binder ($149)
"dot-audit-prep": MCS150UpdateHandler, # admin-assisted (document prep)
"dot-full-compliance": MCS150UpdateHandler, # fans out to individual services
"usdot-reactivation": MCS150UpdateHandler, # same FMCSA submission flow

View file

@ -0,0 +1,178 @@
"""
DOT Drug & Alcohol Compliance Program handler ($149).
Instant-delivery service: when a motor carrier orders the program we
generate a complete, print-ready PDF "binder" and email it to them
automatically (no admin step). The binder bundles the written testing
policy, program-management instructions, supervisor-training materials and
access, EAP / rehab / SAP resources, regulation citations, random-testing
instructions, required forms, and recordkeeping guidance.
Policy variant (DOT operating administration) selection:
For a trucking carrier the program is FMCSA (49 CFR Part 382) that is
the default. If the customer's operation falls under a different DOT mode
(FRA, PHMSA, FTA, FAA, USCG) we honor an explicit ``dot_da_mode`` value in
the intake data. An optional ``state_dfwp`` value appends a state
Drug-Free Workplace addendum.
Returns the local PDF path so job_server uploads it to MinIO and the
INSTANT_DELIVERY path emails it to the customer.
"""
from __future__ import annotations
import json
import logging
import os
import tempfile
from datetime import datetime
from .base_handler import BaseServiceHandler
LOG = logging.getLogger("workers.services.dot_drug_alcohol")
# DOT operating administrations we can build a binder for. FMCSA is the
# trucking default; everything else requires an explicit intake override.
_VALID_MODES = {"fmcsa", "fra", "phmsa", "fta", "faa", "uscg"}
class DrugAlcoholProgramHandler(BaseServiceHandler):
"""Generate and instant-deliver the DOT D&A Compliance Program binder."""
SERVICE_SLUG = "dot-drug-alcohol"
SERVICE_NAME = "DOT Drug & Alcohol Compliance Program"
REQUIRES_LLM = False
async def process(self, order_data: dict) -> list[str]:
order_number = order_data.get("order_number") or order_data.get("name", "")
LOG.info("[%s] Building DOT D&A compliance binder", order_number)
intake = order_data.get("intake_data") or {}
if isinstance(intake, str):
try:
intake = json.loads(intake)
except (TypeError, ValueError):
intake = {}
carrier_name = (
intake.get("legal_name")
or intake.get("entity_name")
or order_data.get("customer_name", "")
).strip()
dot_number = str(intake.get("dot_number", "")).strip()
cdl_drivers = intake.get("cdl_drivers", "")
owner_operators = intake.get("owner_operators", "")
der_name = (intake.get("der_name") or "").strip()
current_provider = (intake.get("current_da_provider") or "").strip()
# ── Policy variant (DOT mode) selection ──────────────────────────
mode = self._resolve_mode(intake)
# Optional state Drug-Free Workplace addendum. Accept either an
# explicit flag/state value or derive from the carrier's base state
# if the intake marks it as a DFWP state.
state_dfwp = self._resolve_state_dfwp(intake)
# The C-TPA / consortium that administers the program. If the
# customer already has a provider, name it; otherwise default to our
# managed consortium.
provider_name = current_provider or "Performance West Consortium / C-TPA"
# ── Guard: need at minimum the carrier name to personalize ───────
if not carrier_name:
LOG.warning(
"[%s] No carrier name in intake — pausing for intake", order_number
)
self._request_intake(order_data)
return []
# ── Generate the binder ──────────────────────────────────────────
work_dir = self._safe_work_dir()
date_str = datetime.now().strftime("%Y%m%d")
safe_name = "".join(
c for c in carrier_name if c.isalnum() or c in (" ", "-", "_")
).strip().replace(" ", "_")[:40] or "carrier"
out_path = os.path.join(
work_dir, f"DOT_DA_Compliance_Binder_{safe_name}_{date_str}.pdf"
)
from scripts.document_gen.templates.dot_da_binder_generator import (
generate_da_binder,
)
result = generate_da_binder(
output_path=out_path,
carrier_name=carrier_name,
dot_number=dot_number,
mode=mode,
cdl_drivers=cdl_drivers,
owner_operators=owner_operators,
der_name=der_name,
der_title="Designated Employer Representative (DER)",
provider_name=provider_name,
state_dfwp=state_dfwp,
)
if not result:
LOG.error("[%s] D&A binder generation returned no file", order_number)
return []
LOG.info(
"[%s] D&A binder ready (mode=%s, state_dfwp=%s) -> %s",
order_number, mode, state_dfwp or "none", result,
)
return [result]
# ------------------------------------------------------------------ #
# Variant resolution
# ------------------------------------------------------------------ #
def _resolve_mode(self, intake: dict) -> str:
"""Pick the DOT operating-administration variant.
Trucking carriers are FMCSA (49 CFR Part 382) by default. Honor an
explicit ``dot_da_mode`` override for the rare non-FMCSA operation.
"""
raw = (intake.get("dot_da_mode") or intake.get("dot_mode") or "").lower().strip()
if raw in _VALID_MODES:
return raw
# A few human-friendly aliases customers might supply.
aliases = {
"trucking": "fmcsa", "motor carrier": "fmcsa", "cdl": "fmcsa",
"rail": "fra", "railroad": "fra",
"pipeline": "phmsa", "transit": "fta", "bus": "fta",
"aviation": "faa", "air": "faa",
"maritime": "uscg", "marine": "uscg", "vessel": "uscg",
}
return aliases.get(raw, "fmcsa")
def _resolve_state_dfwp(self, intake: dict) -> str:
"""Return a state name if a Drug-Free Workplace addendum is wanted."""
explicit = (intake.get("state_dfwp") or "").strip()
if explicit:
return explicit
# If the customer opted into a DFWP add-on, use their base/operating
# state. Otherwise omit the addendum (DOT program stands alone).
if str(intake.get("include_state_dfwp", "")).lower() in ("1", "true", "yes"):
return (
intake.get("base_state")
or intake.get("address_state")
or ""
).strip()
return ""
# ------------------------------------------------------------------ #
# Helpers
# ------------------------------------------------------------------ #
def _safe_work_dir(self) -> str:
try:
return self._make_work_dir()
except Exception:
return tempfile.mkdtemp(prefix="da_binder_")
def _request_intake(self, order_data: dict) -> None:
"""Best-effort: nudge for intake when carrier name is missing."""
try:
fn = getattr(self, "_request_entity_intake", None)
if callable(fn):
fn(order_data)
except Exception as exc:
LOG.warning("Could not request intake: %s", exc)