instruktazh-bezopasnost-ts/generate_pdf.py

461 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Генерация PDF версии инструктажа по безопасности ТС."""
from fpdf import FPDF
from fpdf.enums import XPos, YPos
class SafetyBriefingPDF(FPDF):
def __init__(self):
super().__init__(orientation="P", unit="mm", format="A4")
self.set_auto_page_break(True, 20)
color_ink = (15, 18, 24)
color_red = (229, 57, 53)
color_gray = (91, 101, 115)
color_gray_100 = (242, 244, 247)
color_white = (255, 255, 255)
color_amber = (230, 81, 0)
color_green = (46, 125, 50)
color_cyan = (0, 229, 255)
# We register DejaVu which supports Cyrillic.
font_dir = "/usr/share/fonts/truetype/dejavu/"
self.add_font("DejaVu", "", font_dir + "DejaVuSans.ttf")
self.add_font("DejaVu", "B", font_dir + "DejaVuSans-Bold.ttf")
self.c = {
"ink": color_ink,
"red": color_red,
"gray": color_gray,
"gray_bg": color_gray_100,
"white": color_white,
"amber": color_amber,
"green": color_green,
"cyan": color_cyan,
}
# ── helpers ──────────────────────────────────────────────
def add_header_page(self):
"""Первая страница — титульный заголовок."""
self.add_page()
m = 15 # боковые поля
w = self.w - 2 * m
# Тёмный заголовок
self.set_fill_color(*self.c["ink"])
self.rect(0, 0, self.w, 72, "F")
# Красная полоса
self.set_fill_color(*self.c["red"])
self.rect(0, 72, self.w, 3, "F")
# Плашка «ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ»
self.set_y(16)
self.set_font("DejaVu", "B", 9)
self.set_text_color(*self.c["white"])
self.set_fill_color(*self.c["red"])
badge_w = self.get_string_width(" ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ ") + 6
self.set_x(m)
self.cell(badge_w, 6, " ВНЕПЛАНОВЫЙ ИНСТРУКТАЖ ", fill=True, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
# Заголовок
self.set_y(28)
self.set_x(m)
self.set_font("DejaVu", "B", 18)
self.set_text_color(*self.c["white"])
self.multi_cell(w, 8, "Обеспечение безопасности эксплуатации транспортных средств", align="L")
# Подзаголовок
self.set_y(52)
self.set_x(m)
self.set_font("DejaVu", "", 9)
self.set_text_color(154, 163, 178)
self.multi_cell(w, 5,
"Недопущение оставления ключей в замке зажигания и свободного доступа\n"
"посторонних лиц к управлению транспортными средствами")
# Мета
self.set_y(90)
self.set_x(m)
self.set_text_color(*self.c["ink"])
self.set_font("DejaVu", "", 9)
self.cell(90, 6, "Дата инструктажа: _______________")
self.cell(0, 6, "Провёл: _______________", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
def section_title(self, title):
"""Заголовок секции с подчёркиванием."""
self.ln(4)
self.set_font("DejaVu", "B", 13)
self.set_text_color(*self.c["ink"])
self.cell(0, 8, title, new_x="LMARGIN", new_y="NEXT")
y = self.get_y()
self.set_draw_color(*self.c["gray_bg"])
self.set_line_width(0.4)
self.line(self.l_margin, y + 2, self.w - self.r_margin, y + 2)
self.ln(5)
def body_text(self, text):
self.set_font("DejaVu", "", 10)
self.set_text_color(*self.c["ink"])
self.multi_cell(0, 5.5, text, align="L")
self.ln(1)
def bold_text(self, text):
self.set_font("DejaVu", "B", 10)
self.set_text_color(*self.c["ink"])
self.multi_cell(0, 5.5, text, align="L")
def sub_title(self, title):
self.ln(2)
self.set_font("DejaVu", "B", 10.5)
self.set_text_color(*self.c["ink"])
self.cell(0, 6, title, new_x="LMARGIN", new_y="NEXT")
self.ln(2)
def bullet(self, text, color=None):
x0 = self.get_x()
self.set_font("DejaVu", "", 10)
self.set_text_color(*self.c["ink"])
bx = self.l_margin + 4
self.set_x(bx)
# bullet marker
self.set_font("DejaVu", "", 10)
marker_color = color or self.c["ink"]
self.set_text_color(*marker_color)
self.set_x(self.l_margin + 1)
self.cell(5, 5.5, "\u25a0")
# text
self.set_text_color(*self.c["ink"])
self.set_x(bx + 6)
self.multi_cell(self.w - self.r_margin - bx - 6, 5.5, text)
self.ln(1)
def callout_box(self, text, border_color, bg_color):
self.set_fill_color(*bg_color)
self.set_draw_color(*border_color)
self.set_line_width(0.6)
m = self.l_margin
w = self.w - m - self.r_margin
y0 = self.get_y()
lines = text.split("\n")
n = len(lines)
h = 10 + n * 5.5 + 4
self.rect(m, y0, w, h, "DF")
self.set_draw_color(*border_color)
self.set_line_width(0.6)
self.line(m, y0, m, y0 + h)
self.set_xy(m + 5, y0 + 3)
self.set_font("DejaVu", "", 9.5)
self.set_text_color(*self.c["ink"])
for line in lines:
self.set_x(m + 5)
self.cell(w - 10, 5.5, line, new_x="LMARGIN", new_y="NEXT")
self.set_y(y0 + h + 3)
def checkbox_line(self, text="", x=None):
self.set_font("DejaVu", "", 10)
self.set_text_color(*self.c["ink"])
self.cell(6, 6, "\u2610")
self.cell(0, 6, text, new_x="LMARGIN", new_y="NEXT")
def centered_text(self, text, font_style="", size=10):
self.set_font("DejaVu", font_style, size)
self.set_text_color(*self.c["ink"])
self.cell(0, 7, text, align="C", new_x="LMARGIN", new_y="NEXT")
def _measure_lines(self, w, line_h, text):
"""Return number of lines multi_cell would produce."""
return len(self.multi_cell(w, line_h, text, dry_run=True, output="LINES"))
# ══════════════════════════════════════════════════════════
def build():
pdf = SafetyBriefingPDF()
m = 15 # боковое поле
# ── Титул ──
pdf.add_header_page()
# ══ 1. ОСНОВАНИЕ И ЦЕЛЬ ══
pdf.section_title("1. Основание и цель инструктажа")
pdf.body_text(
"Настоящий внеплановый инструктаж проводится в связи с необходимостью усиления мер "
"по обеспечению безопасности дорожного движения и сохранности имущества организации."
)
pdf.callout_box(
"Повод: выявленные случаи / риск оставления ключей в замке зажигания\n"
"транспортных средств (ТС), что создаёт свободный доступ посторонних лиц\n"
"к управлению ТС, угон, а также использование ТС не по назначению.",
pdf.c["red"], (255, 240, 240),
)
pdf.body_text(
"Цель: довести до всех категорий водителей и ответственных лиц категорические "
"требования по исключению практики оставления ключей в замке зажигания и обеспечению "
"контроля доступа к транспортным средствам."
)
# ══ 2. НОРМАТИВНАЯ БАЗА ══
pdf.section_title("2. Нормативная база")
for item in [
"Закон Республики Казахстан от 17 апреля 2014 г. № 194-V ЗРК «О дорожном движении»",
"Приказ Министра внутренних дел РК от 30 июня 2023 г. № 534 «Об утверждении Правил дорожного движения, Основных положений по допуску транспортных средств к эксплуатации»",
"Приказ и.о. Министра транспорта и коммуникаций РК от 4 марта 2005 г. № 114-I «Об утверждении Правил безопасности и охраны труда на автомобильном транспорте»",
"Кодекс РК об административных правонарушениях от 5 июля 2014 г. № 235-V ЗРК",
"Трудовой кодекс РК от 23 ноября 2015 г. № 414-V ЗРК",
"Правила внутреннего трудового распорядка и должностные инструкции организации",
"Локальные акты организации по охране труда и безопасности на транспорте",
]:
pdf.bullet(item, pdf.c["green"])
# ══ 3. КАТЕГОРИИ ЛИЦ ══
pdf.section_title("3. Категории инструктируемых лиц")
pdf.sub_title("Водители")
pdf.body_text("Штатные водители, закреплённые за конкретными ТС, выполняющие рейсы по маршрутам и заданиям организации.")
pdf.sub_title("Водители-совместители")
pdf.body_text("Сотрудники, совмещающие обязанности водителя с основной должностью, допущенные к управлению ТС организации.")
pdf.sub_title("Ответственные лица")
pdf.body_text("Лица, ответственные за выпуск ТС на линию, техническое состояние, хранение ТС и ведение документации.")
# ══ 4. ОПИСАНИЕ ПРОБЛЕМЫ И РИСКОВ ══
pdf.section_title("4. Описание проблемы и рисков")
pdf.body_text(
"Оставление ключей в замке зажигания транспортного средства вне зависимости от места нахождения ТС "
"(на территории организации, стоянке, в гараже, на линии) создаёт следующие угрозы:"
)
# таблица рисков
risk_data = [
("Угон ТС посторонними лицами",
"Материальный ущерб организации, уголовная ответственность, срыв производственных задач"),
("Использование ТС не по назначению",
"Расход ГСМ, износ ТС, нарушение трудовой дисциплины"),
("ДТП, совершённое лицом без допуска",
"Ущерб жизни и здоровью третьих лиц, гражданская и уголовная ответственность"),
("Хищение груза / имущества из ТС",
"Прямые финансовые потери, утрата доверия контрагентов"),
("Нарушение требований ОТ и ПДД",
"Штрафы, дисциплинарные взыскания, предписания контролирующих органов"),
]
col1_w = 75
col2_w = pdf.w - pdf.l_margin - pdf.r_margin - col1_w
# header
pdf.set_fill_color(*pdf.c["ink"])
pdf.set_text_color(*pdf.c["white"])
pdf.set_font("DejaVu", "B", 9)
pdf.cell(col1_w, 7, " Риск", border=1, fill=True)
pdf.cell(col2_w, 7, " Последствия", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
# rows
for i, (risk, consequence) in enumerate(risk_data):
pdf.set_font("DejaVu", "", 8.5)
pdf.set_text_color(*pdf.c["ink"])
if i % 2 == 0:
pdf.set_fill_color(*pdf.c["gray_bg"])
else:
pdf.set_fill_color(*pdf.c["white"])
y0 = pdf.get_y()
# measure multi_cell heights
pdf.set_font("DejaVu", "", 8.5)
# compute height
h1 = pdf._measure_lines(col1_w - 2, 4.5, risk) * 4.5
h2 = pdf._measure_lines(col2_w - 2, 4.5, consequence) * 4.5
h = max(h1, h2, 13)
if pdf.get_y() + h > pdf.h - pdf.b_margin:
pdf.add_page()
y0 = pdf.get_y()
# draw
pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"])
pdf.rect(pdf.l_margin, y0, col1_w, h, "DF")
pdf.rect(pdf.l_margin + col1_w, y0, col2_w, h, "DF")
pdf.set_xy(pdf.l_margin + 1, y0 + 1)
pdf.set_text_color(*pdf.c["ink"])
pdf.set_font("DejaVu", "B", 8.5)
pdf.multi_cell(col1_w - 2, 4.5, risk)
pdf.set_xy(pdf.l_margin + col1_w + 1, y0 + 1)
pdf.set_font("DejaVu", "", 8.5)
pdf.multi_cell(col2_w - 2, 4.5, consequence)
pdf.set_y(y0 + h)
pdf.ln(4)
# ══ 5. КАТЕГОРИЧЕСКИЕ ТРЕБОВАНИЯ ══
pdf.section_title("5. Категорические требования для всех категорий")
pdf.callout_box(
"ЗАПРЕЩЕНО: оставлять ключи в замке зажигания ТС при любых обстоятельствах:\n"
" \u2022 при кратковременной остановке (заправка, погрузка/разгрузка)\n"
" \u2022 при стоянке на охраняемой территории организации\n"
" \u2022 при постановке ТС в гараж / бокс\n"
" \u2022 при прогреве двигателя без присутствия водителя в кабине\n"
" \u2022 при передаче ТС другому лицу без оформления путевой документации",
pdf.c["red"], (255, 240, 240),
)
pdf.callout_box(
"ОБЯЗАНОСТЬ: при покидании транспортного средства водитель обязан:\n"
" \u2022 заглушить двигатель\n"
" \u2022 извлечь ключ из замка зажигания\n"
" \u2022 убрать ключ в надёжное место (карман одежды, сумку)\n"
" \u2022 убедиться, что ТС обездвижено (стояночный тормоз, передача)\n"
" \u2022 закрыть двери кабины/салона на замок",
pdf.c["amber"], (255, 248, 225),
)
# ══ 6. ТРЕБОВАНИЯ ПО КАТЕГОРИЯМ ══
pdf.section_title("6. Требования по категориям")
pdf.sub_title("6.1 Водители (штатные)")
for item in [
"Ключи от закреплённого ТС хранятся только у водителя, которому выданы под подпись.",
"При возвращении на базу — сдать ключи диспетчеру / ответственному лицу под роспись в журнале.",
"Запрещена передача ключей третьим лицам (включая коллег) без разрешения ответственного лица.",
"При обнаружении ключей, оставленных в замке зажигания другого ТС — немедленно сообщить диспетчеру и изъять ключи.",
]:
pdf.bullet(item)
pdf.sub_title("6.2 Водители-совместители")
for item in [
"Ключи от ТС получать у ответственного лица на конкретный рейс/задание под роспись.",
"По возвращении сдать ключи немедленно, не оставляя их в кабине.",
"Запрещено брать ТС без оформленного путевого листа и отметки механика о допуске.",
"Запрещено хранить дубликаты ключей от ТС в личных вещах без согласования с ответственным лицом.",
]:
pdf.bullet(item)
pdf.sub_title("6.3 Ответственные лица (диспетчеры, механики, начальники колонн)")
for item in [
"Вести журнал выдачи-возврата ключей от ТС с подписями водителей и отметками времени.",
"Ежедневно проверять наличие ключей в месте хранения (запираемый ящик / сейф).",
"Контролировать, что выпуск ТС осуществляется только при наличии путевого листа и отметки о медосмотре.",
"Незамедлительно докладывать руководству о фактах оставления ключей в замке зажигания.",
"Обеспечить хранение резервных (дублирующих) ключей в опечатанном виде с записью в журнале.",
"Проводить внезапные проверки стоянки ТС на предмет наличия ключей в замке зажигания.",
]:
pdf.bullet(item)
# ══ 7. ПОРЯДОК ХРАНЕНИЯ И УЧЁТА КЛЮЧЕЙ ══
pdf.section_title("7. Порядок хранения и учёта ключей от ТС")
table_data = [
("Операция", "Действие", "Документ", "Ответственный"),
("Выдача ключей перед рейсом", "Выдать водителю лично под роспись", "Журнал выдачи ключей", "Диспетчер"),
("Возврат ключей после рейса", "Принять, проверить, сделать запись", "Журнал выдачи ключей", "Диспетчер"),
("Хранение в нерабочее время", "Запираемый шкаф / сейф в диспетчерской", "", "Диспетчер / охрана"),
("Дубликаты ключей", "Опечатанный пенал с биркой (номер ТС)", "Опечатанный пенал", "Руководитель транспортного отдела"),
("Утеря / поломка ключа", "Акт + выдача дубликата + замена замка", "Акт об утере", "Комиссия (не менее 2 чел.)"),
]
cols = [45, 55, 42, 52]
col_total = sum(cols)
cw = []
avail = pdf.w - pdf.l_margin - pdf.r_margin
for c in cols:
cw.append(avail * c / col_total)
for ri, row in enumerate(table_data):
if pdf.get_y() > pdf.h - 20:
pdf.add_page()
for ci, cell in enumerate(row):
if ri == 0:
pdf.set_fill_color(*pdf.c["ink"])
pdf.set_text_color(*pdf.c["white"])
pdf.set_font("DejaVu", "B", 7.5)
else:
pdf.set_fill_color(pdf.c["gray_bg"] if ri % 2 == 0 else pdf.c["white"])
pdf.set_text_color(*pdf.c["ink"])
pdf.set_font("DejaVu", "", 7.5)
pdf.cell(cw[ci], 7, " " + cell, border=1, fill=True)
pdf.ln()
pdf.ln(4)
# ══ 8. ОТВЕТСТВЕННОСТЬ ══
pdf.section_title("8. Ответственность за нарушения")
resp_data = [
("Первичное оставление ключей (без последствий)",
"Дисциплинарное взыскание (замечание / выговор) + внеочередной инструктаж"),
("Повторное оставление ключей",
"Строгий выговор + лишение премии"),
("Систематические нарушения (3 и более)",
"Отстранение от управления ТС, расторжение трудового договора"),
("Оставление ключей, повлёкшее угон / ДТП",
"Материальная ответственность в полном объёме ущерба + передача материалов в правоохранительные органы"),
("Сокрытие факта оставления ключей",
"Увольнение + материальная ответственность"),
]
col1_w_r = 80
col2_w_r = pdf.w - pdf.l_margin - pdf.r_margin - col1_w_r
# header
pdf.set_fill_color(*pdf.c["ink"])
pdf.set_text_color(*pdf.c["white"])
pdf.set_font("DejaVu", "B", 9)
pdf.cell(col1_w_r, 7, " Нарушение", border=1, fill=True)
pdf.cell(col2_w_r, 7, " Мера ответственности", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
for i, (violation, measure) in enumerate(resp_data):
if pdf.get_y() > pdf.h - 20:
pdf.add_page()
# compute height
pdf.set_font("DejaVu", "", 8.5)
h1 = pdf._measure_lines(col1_w_r - 2, 4.5, violation) * 4.5
h2 = pdf._measure_lines(col2_w_r - 2, 4.5, measure) * 4.5
h = max(h1, h2, 13)
y0 = pdf.get_y()
if y0 + h > pdf.h - pdf.b_margin:
pdf.add_page()
y0 = pdf.get_y()
pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"])
pdf.rect(pdf.l_margin, y0, col1_w_r, h, "DF")
pdf.rect(pdf.l_margin + col1_w_r, y0, col2_w_r, h, "DF")
pdf.set_xy(pdf.l_margin + 1, y0 + 1)
pdf.set_font("DejaVu", "B", 8.5)
pdf.set_text_color(*pdf.c["ink"])
pdf.multi_cell(col1_w_r - 2, 4.5, violation)
pdf.set_xy(pdf.l_margin + col1_w_r + 1, y0 + 1)
pdf.set_font("DejaVu", "", 8.5)
pdf.multi_cell(col2_w_r - 2, 4.5, measure)
pdf.set_y(y0 + h)
pdf.ln(4)
# ══ 9. ЛИСТ ОЗНАКОМЛЕНИЯ ══
pdf.section_title("9. Лист ознакомления с инструктажом")
pdf.body_text(
"Своей подписью подтверждаю, что с содержанием внепланового инструктажа ознакомлен(а), "
"требования и меру ответственности осознаю, обязуюсь выполнять."
)
pdf.ln(4)
# Таблица подписей
sig_header = ["Фамилия И.О.", "Должность / Категория", "Подпись и дата"]
sig_w = [55, 75, (pdf.w - pdf.l_margin - pdf.r_margin - 130)] # dynamic
# header
pdf.set_fill_color(*pdf.c["ink"])
pdf.set_text_color(*pdf.c["white"])
pdf.set_font("DejaVu", "B", 9)
pdf.cell(sig_w[0], 7, " " + sig_header[0], border=1, fill=True)
pdf.cell(sig_w[1], 7, " " + sig_header[1], border=1, fill=True)
pdf.cell(sig_w[2], 7, " " + sig_header[2], border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
for i in range(12):
if pdf.get_y() > pdf.h - 20:
pdf.add_page()
pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"])
pdf.set_text_color(*pdf.c["ink"])
pdf.set_font("DejaVu", "", 9)
pdf.cell(sig_w[0], 9, "", border=1, fill=True)
pdf.cell(sig_w[1], 9, "", border=1, fill=True)
pdf.cell(sig_w[2], 9, "", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
pdf.ln(6)
pdf.body_text("Инструктаж провёл: _______________________________________ (должность, Ф.И.О., подпись, дата)")
# ── SAVE ──
out = "Инструктаж_Безопасность_ТС_Ключи_зажигания.pdf"
pdf.output(out)
print(f"PDF сохранён: {out}")
if __name__ == "__main__":
build()