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.
872 lines
44 KiB
Python
872 lines
44 KiB
Python
"""
|
||
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 & 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 & "
|
||
"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 & Return-to-Duty.</b> What happens "
|
||
"after a positive test, and Substance Abuse Professional access.",
|
||
"<b>Section 7 — EAP, Rehab & 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 & 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 & 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 & 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 & 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 & 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 & Acknowledgment.",
|
||
"<b>Form B</b> — Pre-Employment Testing Consent & 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 & 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 & Acknowledgment", h2))
|
||
story.append(Paragraph(
|
||
f"I acknowledge that I have received, read, and understand the "
|
||
f"{carrier_name or 'Company'} Drug & 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))
|