From 843a5bfacb29c84518c86f0a6806a90867be52e6 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 2 Jun 2026 21:54:06 -0500 Subject: [PATCH] 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(). --- .../templates/dot_da_binder_generator.py | 289 +++++++++++++++++- 1 file changed, 280 insertions(+), 9 deletions(-) diff --git a/scripts/document_gen/templates/dot_da_binder_generator.py b/scripts/document_gen/templates/dot_da_binder_generator.py index bedf828..c49507b 100644 --- a/scripts/document_gen/templates/dot_da_binder_generator.py +++ b/scripts/document_gen/templates/dot_da_binder_generator.py @@ -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]