DOT D&A binder: add DER Quick-Start checklist, two-column vendor directory, flow sections
- Add a one-page 'DER Quick-Start Checklist' tear-off as the first content page (set-up-once / every-hire / ongoing checkboxes, each pointing to the relevant section or form). - Add a two-column 'Suggested Vendors & Resources' directory page: C-TPA/ consortium, collection sites & labs, MRO, SAP, supervisor training, and (mode- aware) FMCSA Clearinghouse or DOT resources, plus employee help lines. Marked as examples not endorsements; mode-aware. - Remove forced page breaks between consecutive content sections (now a light section rule) so they flow continuously; page breaks kept only for the cover, quick-start, TOC, each form, the vendor page, regulations, and the addendum. - New builder helpers: section_rule(), checkbox(), two_col_panels().
This commit is contained in:
parent
501e417584
commit
843a5bfacb
1 changed files with 280 additions and 9 deletions
|
|
@ -302,6 +302,74 @@ class _B:
|
|||
def page_break(self):
|
||||
self.doc.add_page_break()
|
||||
|
||||
def section_rule(self, space_before=14, space_after=2):
|
||||
"""A light divider between sections that share a page (no page break)."""
|
||||
p = self.doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(space_before)
|
||||
p.paragraph_format.space_after = Pt(space_after)
|
||||
pPr = p._p.get_or_add_pPr()
|
||||
pbdr = OxmlElement("w:pBdr")
|
||||
bottom = OxmlElement("w:bottom")
|
||||
bottom.set(qn("w:val"), "single")
|
||||
bottom.set(qn("w:sz"), "4")
|
||||
bottom.set(qn("w:space"), "1")
|
||||
bottom.set(qn("w:color"), "DBE2EA")
|
||||
pbdr.append(bottom)
|
||||
pPr.append(pbdr)
|
||||
return p
|
||||
|
||||
def _panel_into_cell(self, cell, title, lead, items):
|
||||
"""Render a titled resource panel inside a table cell."""
|
||||
cell.text = ""
|
||||
tp = cell.paragraphs[0]
|
||||
tp.paragraph_format.space_after = Pt(2)
|
||||
tr = tp.add_run(title)
|
||||
tr.bold = True
|
||||
tr.font.size = Pt(11)
|
||||
tr.font.color.rgb = NAVY
|
||||
if lead:
|
||||
lp = cell.add_paragraph()
|
||||
lp.paragraph_format.space_after = Pt(3)
|
||||
lr = lp.add_run(lead)
|
||||
lr.italic = True
|
||||
lr.font.size = Pt(8.5)
|
||||
lr.font.color.rgb = SLATE
|
||||
for it in items:
|
||||
ip = cell.add_paragraph()
|
||||
ip.paragraph_format.space_after = Pt(2)
|
||||
ip.paragraph_format.left_indent = Inches(0.13)
|
||||
ip.paragraph_format.first_line_indent = Inches(-0.13)
|
||||
bullet = ip.add_run("\u2022 ")
|
||||
bullet.font.size = Pt(9)
|
||||
bullet.font.color.rgb = BLUE
|
||||
_add_runs(ip, it)
|
||||
for r in ip.runs[1:]:
|
||||
r.font.size = Pt(9)
|
||||
r.font.color.rgb = INK
|
||||
|
||||
def two_col_panels(self, panels):
|
||||
"""Lay out (title, lead, [items]) panels in two columns, borderless."""
|
||||
rows = (len(panels) + 1) // 2
|
||||
t = self.doc.add_table(rows=rows, cols=2)
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
t.autofit = False
|
||||
for col in t.columns:
|
||||
for cell in col.cells:
|
||||
cell.width = Inches(3.35)
|
||||
for i, panel in enumerate(panels):
|
||||
r, c = divmod(i, 2)
|
||||
self._panel_into_cell(t.rows[r].cells[c], *panel)
|
||||
tblPr = t._tbl.tblPr
|
||||
borders = OxmlElement("w:tblBorders")
|
||||
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
|
||||
el = OxmlElement(f"w:{edge}")
|
||||
el.set(qn("w:val"), "none")
|
||||
el.set(qn("w:sz"), "0")
|
||||
el.set(qn("w:space"), "0")
|
||||
borders.append(el)
|
||||
tblPr.append(borders)
|
||||
return t
|
||||
|
||||
def spacer(self, pts=8):
|
||||
p = self.doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(pts)
|
||||
|
|
@ -322,6 +390,21 @@ class _B:
|
|||
self.fill_line((label if i == 0 else "") +
|
||||
" " + "_" * 78, gap=12)
|
||||
|
||||
def checkbox(self, text, *, size=10.5, gap=5, indent=0.3):
|
||||
"""A checklist line beginning with an open box glyph."""
|
||||
p = self.doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(gap)
|
||||
p.paragraph_format.left_indent = Inches(indent)
|
||||
p.paragraph_format.first_line_indent = Inches(-0.22)
|
||||
box = p.add_run("\u2750 ") # open ballot box
|
||||
box.font.size = Pt(size + 1)
|
||||
box.font.color.rgb = NAVY
|
||||
_add_runs(p, text)
|
||||
for r in p.runs[1:]:
|
||||
r.font.size = Pt(size)
|
||||
r.font.color.rgb = INK
|
||||
return p
|
||||
|
||||
|
||||
def generate_da_binder(
|
||||
*,
|
||||
|
|
@ -407,8 +490,14 @@ def generate_da_binder(
|
|||
"maintaining its own program. Have counsel review before adoption.")
|
||||
b.page_break()
|
||||
|
||||
# ── DER Quick-Start Checklist (tear-off first page) ─────────────────────
|
||||
_der_quickstart(b, meta, der_name, der_title, provider_name, random_rate)
|
||||
|
||||
# ── Table of contents ───────────────────────────────────────────────────
|
||||
b.h1("What's Inside This Binder")
|
||||
b.body(
|
||||
"Start with the **DER Quick-Start Checklist** on the previous page, "
|
||||
"then use the sections below for the detail.")
|
||||
b.bullets([
|
||||
"**Section 1 — How to Manage Your Program.** Step-by-step setup and "
|
||||
"ongoing operating instructions for the Designated Employer "
|
||||
|
|
@ -434,6 +523,8 @@ def generate_da_binder(
|
|||
"answers: owner-operators, audits and penalties, problem test results, "
|
||||
"prescriptions/marijuana/CBD, what counts as a refusal, costs, and the "
|
||||
"DER's do's and don'ts.",
|
||||
"**Suggested Vendors & Resources.** A two-column directory of the "
|
||||
"consortiums, labs, MRO/SAP, and training providers you'll need.",
|
||||
])
|
||||
if state_dfwp:
|
||||
b.body(
|
||||
|
|
@ -566,7 +657,7 @@ def generate_da_binder(
|
|||
"them from the random pool and note the departure on your roster "
|
||||
"(Form G). Keep their records for the required retention period "
|
||||
"(Section 8).")
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 2 — Written policy ──────────────────────────────────────────
|
||||
b.h1("Section 2 — Written Drug & Alcohol Testing Policy")
|
||||
|
|
@ -686,7 +777,7 @@ def generate_da_binder(
|
|||
"_______________________")
|
||||
b.fill_line("Effective date: __________________ USDOT #: "
|
||||
f"{dot_number or '________________'}")
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 3 — When testing is required ───────────────────────────────
|
||||
b.h1("Section 3 — When Testing Is Required")
|
||||
|
|
@ -737,7 +828,7 @@ def generate_da_binder(
|
|||
"program in the prior 30 days, a DOT drug test in the prior 6 months or "
|
||||
"random-pool participation for the prior 12 months, and no knowledge of "
|
||||
"a prior-employer violation). When in doubt, test.")
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 4 — Random testing ─────────────────────────────────────────
|
||||
b.h1("Section 4 — Random Testing Program")
|
||||
|
|
@ -773,7 +864,7 @@ def generate_da_binder(
|
|||
"after the employee performs safety-sensitive functions.",
|
||||
"Keep the consortium's selection and results records (Section 8).",
|
||||
])
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 5 — Supervisor training ────────────────────────────────────
|
||||
b.h1("Section 5 — Supervisor Reasonable-Suspicion Training")
|
||||
|
|
@ -819,7 +910,7 @@ def generate_da_binder(
|
|||
"**What you get:** self-paced online modules, a knowledge check, and a "
|
||||
"dated certificate of completion to keep on file (record on Form F).",
|
||||
])
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 6 — Violations / SAP ───────────────────────────────────────
|
||||
b.h1("Section 6 — Violations, SAP & Return-to-Duty")
|
||||
|
|
@ -863,7 +954,7 @@ def generate_da_binder(
|
|||
"with an unresolved Clearinghouse violation is 'prohibited' and may "
|
||||
"not operate a CMV until the return-to-duty requirements are met "
|
||||
"and recorded.")
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 7 — EAP / rehab ────────────────────────────────────────────
|
||||
b.h1("Section 7 — EAP, Rehab & Treatment Resources")
|
||||
|
|
@ -904,7 +995,7 @@ def generate_da_binder(
|
|||
"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.")
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 8 — Recordkeeping ──────────────────────────────────────────
|
||||
b.h1("Section 8 — Recordkeeping")
|
||||
|
|
@ -935,7 +1026,7 @@ def generate_da_binder(
|
|||
"Store records so they are confidential and retrievable. Your C-TPA "
|
||||
"keeps a parallel set of consortium records; request a copy any time.",
|
||||
italic=True)
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
|
||||
# ── Section 9 — Forms ──────────────────────────────────────────────────
|
||||
b.h1("Section 9 — Required Compliance Forms")
|
||||
|
|
@ -994,6 +1085,9 @@ def generate_da_binder(
|
|||
# ── Section 11 — Practical guidance for the administrator ───────────────
|
||||
_section_practical_guidance(b, meta, company, random_rate)
|
||||
|
||||
# ── Suggested vendors & resources (two-column) ──────────────────────────
|
||||
_vendor_directory(b, meta)
|
||||
|
||||
# ── Optional state DFWP addendum ───────────────────────────────────────
|
||||
if state_dfwp:
|
||||
b.page_break()
|
||||
|
|
@ -1028,9 +1122,78 @@ def generate_da_binder(
|
|||
return str(out)
|
||||
|
||||
|
||||
# ── DER Quick-Start Checklist (one-page tear-off) ───────────────────────────
|
||||
def _der_quickstart(b, meta, der_name, der_title, provider_name, random_rate):
|
||||
b.h1("DER Quick-Start Checklist")
|
||||
b.body(
|
||||
"A one-page summary for the Designated Employer Representative (DER). "
|
||||
"Work top to bottom: do the SET-UP items once, then run the EVERY-HIRE "
|
||||
"and ONGOING items continuously. Each item points to the section or "
|
||||
"form with the detail. Tear this page out and keep it handy.",
|
||||
italic=True)
|
||||
|
||||
b.h3("Set up once")
|
||||
setup = [
|
||||
"**Name the DER and a backup** (cover page). DER: "
|
||||
f"{der_name or '____________________'}.",
|
||||
"**Adopt & sign the written policy** (Section 2).",
|
||||
f"**Join / confirm your consortium (C-TPA):** {provider_name} "
|
||||
"(Sections 1 & 4).",
|
||||
"**Train every supervisor** 120 minutes before any reasonable-suspicion "
|
||||
"decision (Section 5; Form F).",
|
||||
]
|
||||
if meta.get("clearinghouse"):
|
||||
setup.append(
|
||||
"**Register with the FMCSA Clearinghouse** and designate your "
|
||||
"C-TPA (Section 1).")
|
||||
for item in setup:
|
||||
b.checkbox(item)
|
||||
|
||||
b.h3("Every new hire (before they drive)")
|
||||
hire = [
|
||||
"**Collect driver info & add to the random pool** (Form G).",
|
||||
"**Get signed Form A** (policy ack.) **and Form B** (consent / prior-"
|
||||
"employer inquiry).",
|
||||
]
|
||||
if meta.get("clearinghouse"):
|
||||
hire.append(
|
||||
"**Run the Clearinghouse pre-employment full query** — confirm "
|
||||
"**not prohibited**.")
|
||||
hire += [
|
||||
"**Send for the pre-employment drug test;** wait for the MRO **verified "
|
||||
"negative** before any safety-sensitive work (Section 3).",
|
||||
]
|
||||
for item in hire:
|
||||
b.checkbox(item)
|
||||
|
||||
b.h3("Ongoing")
|
||||
ongoing = [
|
||||
f"**Random testing** all year at {random_rate}; send selected drivers "
|
||||
"**immediately**, no warning (Section 4).",
|
||||
"**Reasonable suspicion** — trained supervisor documents it (Form D).",
|
||||
"**Post-accident** — use the decision worksheet at the scene (Form E).",
|
||||
"**Keep the roster current** (add hires, remove departures) (Form G).",
|
||||
"**Act on any positive / 0.04+ / refusal the SAME day** — remove from "
|
||||
"duty, start the SAP / return-to-duty process (Section 6).",
|
||||
"**Keep records** confidential and for the required period (Section 8).",
|
||||
]
|
||||
if meta.get("clearinghouse"):
|
||||
ongoing.append(
|
||||
"**Annual Clearinghouse limited query** on every CDL driver; "
|
||||
"**report** violations within the required timeframes (Section 1).")
|
||||
for item in ongoing:
|
||||
b.checkbox(item)
|
||||
|
||||
b.body(
|
||||
"**Stuck or unsure?** See Section 11 (owner-operators, audits, problem "
|
||||
"results, marijuana/CBD, refusals) or call your C-TPA/MRO. Questions: "
|
||||
"compliance@performancewest.com.")
|
||||
b.page_break()
|
||||
|
||||
|
||||
# ── Section 11 — Practical guidance ─────────────────────────────────────────
|
||||
def _section_practical_guidance(b, meta, company, random_rate):
|
||||
b.page_break()
|
||||
b.section_rule()
|
||||
b.h1("Section 11 — Practical Guidance for the Administrator")
|
||||
b.body(
|
||||
"The regulations tell you what to do; this section covers the real-"
|
||||
|
|
@ -1174,6 +1337,114 @@ def _section_practical_guidance(b, meta, company, random_rate):
|
|||
])
|
||||
|
||||
|
||||
# ── Suggested vendors & resources (two-column directory) ────────────────────
|
||||
def _vendor_directory(b, meta):
|
||||
b.page_break()
|
||||
b.h1("Suggested Vendors & Resources")
|
||||
b.body(
|
||||
"Setting up your program means lining up a few service providers. The "
|
||||
"panels below group the vendors a carrier typically needs, with "
|
||||
"well-known national options as a starting point. These are examples "
|
||||
"to help you shop, **not endorsements** — compare service, coverage, "
|
||||
"and price, and confirm each provider meets DOT requirements. The "
|
||||
"simplest path for most small carriers is a single Consortium/Third-"
|
||||
"Party Administrator (C-TPA) that bundles the pool, scheduling, "
|
||||
"collection network, lab, and MRO.", italic=True)
|
||||
|
||||
panels = [
|
||||
(
|
||||
"Consortium / C-TPA (bundles everything)",
|
||||
"Manages your random pool, scheduling, collection, lab & MRO.",
|
||||
[
|
||||
"**Performance West** — managed program & C-TPA "
|
||||
"(compliance@performancewest.com)",
|
||||
"**National Drug Screening** — nationaldrugscreening.com",
|
||||
"**US Drug Test Centers** — usdrugtestcenters.com",
|
||||
"**Foundation / DISA Global Solutions** — disa.com",
|
||||
"**National Compliance Management Service (NCMS)** — "
|
||||
"ncmsmro.com",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Collection sites & SAMHSA-certified labs",
|
||||
"Where the specimen is collected and analyzed (DOT 5-panel).",
|
||||
[
|
||||
"**Quest Diagnostics** — questdiagnostics.com (nationwide "
|
||||
"Patient Service Centers)",
|
||||
"**Labcorp** — labcorp.com / labcorpdrugtesting.com",
|
||||
"**Any Lab Test Now** — anylabtestnow.com",
|
||||
"Your C-TPA assigns the nearest approved collection site.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Medical Review Officer (MRO)",
|
||||
"Licensed physician who verifies non-negative results.",
|
||||
[
|
||||
"Usually **included with your C-TPA** — confirm before signing.",
|
||||
"**American Association of MROs (AAMRO)** — aamro.com "
|
||||
"(verify/locate an MRO)",
|
||||
"**NCMS / NDS** MRO services (see C-TPA panel).",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Substance Abuse Professional (SAP)",
|
||||
"Required after a violation, for return-to-duty.",
|
||||
[
|
||||
"**DOT SAP locator & guidance** — transportation.gov/odapc",
|
||||
"**SAPlist.com** — directory of qualified SAPs",
|
||||
"Ask your C-TPA for SAP referrals in the driver's area.",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Supervisor reasonable-suspicion training",
|
||||
"120 minutes (60 alcohol + 60 drugs) before any decision.",
|
||||
[
|
||||
"**Performance West** supervisor training "
|
||||
"(compliance@performancewest.com)",
|
||||
"**J. J. Keller** — jjkeller.com",
|
||||
"**National Drug Screening / US Drug Test Centers** online "
|
||||
"courses (see C-TPA panel).",
|
||||
],
|
||||
),
|
||||
]
|
||||
if meta.get("clearinghouse"):
|
||||
panels.append((
|
||||
"FMCSA Clearinghouse (CDL drivers)",
|
||||
"Register, designate your C-TPA, run queries, report violations.",
|
||||
[
|
||||
"**Official site** — clearinghouse.fmcsa.dot.gov",
|
||||
"Your C-TPA can run queries and reporting on your behalf once "
|
||||
"you designate them.",
|
||||
"**FMCSA help** — 844-955-0207",
|
||||
],
|
||||
))
|
||||
else:
|
||||
panels.append((
|
||||
"Official DOT / agency resources",
|
||||
"Free, authoritative guidance and forms.",
|
||||
[
|
||||
f"**{meta['agency']}** rule: ecfr.gov (search the part number)",
|
||||
"**DOT ODAPC** — transportation.gov/odapc",
|
||||
],
|
||||
))
|
||||
panels.append((
|
||||
"Help & treatment (for employees)",
|
||||
"Give these to employees with the policy (Section 7).",
|
||||
[
|
||||
"**SAMHSA Helpline** — 1-800-662-HELP (4357), 24/7",
|
||||
"**findtreatment.gov** — treatment locator",
|
||||
"**988** Suicide & Crisis Lifeline (call/text)",
|
||||
],
|
||||
))
|
||||
|
||||
b.two_col_panels(panels)
|
||||
b.small(
|
||||
"Listing a provider here is not an endorsement and Performance West "
|
||||
"receives no fee for these listings unless noted. Verify current "
|
||||
"pricing, coverage, and DOT compliance directly with each vendor. "
|
||||
"Need help choosing? Email compliance@performancewest.com.")
|
||||
|
||||
|
||||
# ── Header / footer ─────────────────────────────────────────────────────────
|
||||
def _add_header_footer(doc, meta, carrier_name):
|
||||
section = doc.sections[0]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue