instruktazh-zhara-ts/generate_pdf.py

437 lines
22 KiB
Python
Raw 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 HotWeatherBriefingPDF(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_orange = (255, 109, 0)
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,
"orange": color_orange,
}
def add_header_page(self):
self.add_page()
m = 15
w = self.w - 2 * m
# Оранжевый градиентный заголовок
self.set_fill_color(*self.c["orange"])
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", 16)
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(255, 240, 220)
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 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):
self.set_font("DejaVu", "", 10)
self.set_text_color(*self.c["ink"])
bx = self.l_margin + 4
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")
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_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 _measure_lines(self, w, line_h, text):
return len(self.multi_cell(w, line_h, text, dry_run=True, output="LINES"))
def draw_card(self, emoji, title, text, x, y, w):
"""Draw a single card with emoji, title and body text."""
pad = 8
# Calculate card height
self.set_font("DejaVu", "B", 10)
title_h = 6
self.set_font("DejaVu", "", 9)
n_lines = self._measure_lines(w - 2 * pad, 4.5, text)
body_h = n_lines * 4.5 + 4
card_h = pad + 12 + title_h + body_h + pad # emoji + title + body + padding
# Card background
self.set_fill_color(*self.c["white"])
self.set_draw_color(*self.c["gray_bg"])
self.set_line_width(0.3)
self.rect(x, y, w, card_h, "DF")
# Emoji
self.set_xy(x + pad, y + pad)
self.set_font("DejaVu", "", 20)
self.cell(20, 12, emoji)
# Title
self.set_xy(x + pad, y + pad + 12)
self.set_font("DejaVu", "B", 10)
self.set_text_color(*self.c["ink"])
self.cell(w - 2 * pad, title_h, title)
# Body
self.set_xy(x + pad, y + pad + 12 + title_h + 2)
self.set_font("DejaVu", "", 9)
self.set_text_color(*self.c["gray"])
self.multi_cell(w - 2 * pad, 4.5, text, align="L")
return card_h
def draw_cards_row(self, cards, y, card_w=None):
"""Draw 3 cards in a row. cards = list of (emoji, title, text)."""
n = len(cards)
gap = 12
avail_w = self.w - self.l_margin - self.r_margin
if card_w is None:
card_w = (avail_w - gap * (n - 1)) / n
for i, (emoji, title, text) in enumerate(cards):
x = self.l_margin + i * (card_w + gap)
self.draw_card(emoji, title, text, x, y, card_w)
# Return max height
max_h = 0
for emoji, title, text in cards:
pad = 8
self.set_font("DejaVu", "", 9)
n_lines = self._measure_lines(card_w - 2 * pad, 4.5, text)
body_h = n_lines * 4.5 + 4
card_h = pad + 12 + 6 + body_h + pad
if card_h > max_h:
max_h = card_h
return max_h
def draw_cards_2x2(self, cards, y):
"""Draw 4 cards in a 2x2 grid. cards = list of (emoji, title, text)."""
gap = 12
avail_w = self.w - self.l_margin - self.r_margin
card_w = (avail_w - gap) / 2
max_h = 0
for row in range(2):
for col in range(2):
idx = row * 2 + col
if idx < len(cards):
emoji, title, text = cards[idx]
x = self.l_margin + col * (card_w + gap)
cy = y + row * (max_h + gap if max_h > 0 else 0)
h = self.draw_card(emoji, title, text, x, cy, card_w)
if row == 0 and col == 0:
max_h = h
elif h > max_h:
max_h = h
return max_h * 2 + gap
def build():
pdf = HotWeatherBriefingPDF()
# ── Титул ──
pdf.add_header_page()
# ══ 1. ОСНОВАНИЕ И ЦЕЛЬ ══
pdf.section_title("1. Основание и цель инструктажа")
pdf.body_text(
"Настоящий инструктаж проводится в связи с наступлением жаркого периода года "
"(температура воздуха выше +30\u00b0C) и необходимостью обеспечения безопасности "
"водителей, управляющих транспортными средствами без системы кондиционирования."
)
pdf.callout_box(
"Цель: предотвращение тепловых ударов, обезвоживания и снижения\n"
"работоспособности водителей, исключение ДТП вследствие потери\n"
"управления ТС по причине перегрева организма.",
pdf.c["orange"], (255, 243, 224),
)
pdf.body_text(
"Инструктаж обязателен для всех водителей (штатных и совместителей), "
"эксплуатирующих ТС без кондиционера, а также для лиц, допускающих ТС на линию."
)
# ══ 2. НОРМАТИВНАЯ БАЗА ══
pdf.section_title("2. Нормативная база")
for item in [
"Закон Республики Казахстан от 17 апреля 2014 г. № 194-V ЗРК «О дорожном движении»",
"Приказ Министра внутренних дел РК от 30 июня 2023 г. № 534 «ПДД, Основные положения по допуску ТС к эксплуатации»",
"Приказ и.о. Министра транспорта и коммуникаций РК от 4 марта 2005 г. № 114-I «Правила безопасности и охраны труда на автомобильном транспорте»",
"Трудовой кодекс РК от 23 ноября 2015 г. № 414-V ЗРК (ст. 184186)",
"Санитарные правила и нормы (СанПиН) Республики Казахстан",
"Локальные акты организации по охране труда и безопасности на транспорте",
]:
pdf.bullet(item, pdf.c["green"])
# ══ 3. ЗОНА РИСКА ══
pdf.section_title("3. Зона риска и последствия перегрева")
pdf.body_text(
"Управление ТС без кондиционера в жаркий период представляет особую опасность, "
"поскольку водитель вынужден длительное время находиться в замкнутом, нагретом "
"солнцем пространстве кабины/салона."
)
pdf.callout_box(
"Опасная зона: температура воздуха на улице от +30\u00b0C и выше.\n"
"В закрытой кабине ТС температура может превышать уличную на 1525\u00b0C.",
pdf.c["red"], (255, 240, 240),
)
# Таблица рисков
risk_data = [
("+3034\u00b0C", "+4555\u00b0C", "Повышенная утомляемость, снижение внимания"),
("+3539\u00b0C", "+5565\u00b0C", "Тепловое недомогание, риск обезвоживания"),
("+40\u00b0C и выше", "+65\u00b0C и выше", "Тепловой удар, потеря сознания"),
]
col1_w = 35
col2_w = 35
col3_w = pdf.w - pdf.l_margin - pdf.r_margin - col1_w - col2_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)
pdf.cell(col3_w, 7, " Риск", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
for i, (outdoor, cabin, risk) in enumerate(risk_data):
pdf.set_font("DejaVu", "", 9)
pdf.set_text_color(*pdf.c["ink"])
pdf.set_fill_color(pdf.c["gray_bg"] if i % 2 == 0 else pdf.c["white"])
pdf.cell(col1_w, 7, " " + outdoor, border=1, fill=True)
pdf.cell(col2_w, 7, " " + cabin, border=1, fill=True)
pdf.cell(col3_w, 7, " " + risk, border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
pdf.ln(6)
# Карточки рисков
cards_risk = [
("T", "Тепловой удар", "Потеря сознания, спутанность сознания, судороги. Риск ДТП \u2014 100%."),
("\u2022", "Обезвоживание", "Снижение концентрации внимания, замедление реакции, головокружение."),
("Z", "Гиперсомния", "Повышенная сонливость, \u00abмикрозасыпания\u00bb за рулём \u2014 основная причина лобовых столкновений."),
]
y_cards = pdf.get_y()
cards_h = pdf.draw_cards_row(cards_risk, y_cards)
pdf.set_y(y_cards + cards_h + 4)
# ══ 4. ДО ВЫЕЗДА НА ЛИНИЮ ══
pdf.section_title("4. Действия до выезда на линию")
items = [
"Оценить состояние здоровья. При головной боли, слабости, температуре выше +37\u00b0C — отказаться от выезда, сообщить диспетчеру.",
"Взять запас воды: минимум 2 литра питьевой воды на смену (в термосумке). Запрещается заменять воду сладкими напитками и газировкой.",
"Взять головной убор (панама, кепка с козырьком) для защиты от солнца при выходе из ТС.",
"Взять влажные салфетки / полотенце для обтирания лица и шеи.",
"Проветрить кабину перед выездом (открыть двери на 35 минут).",
"Одежда — лёгкая, светлая, воздухопроницаемая (хлопок). Запрещается тёмная синтетика.",
"Проверить, что окна открываются, вентиляция работает.",
]
for item in items:
pdf.bullet(item)
# ══ 5. ВО ВРЕМЯ ДВИЖЕНИЯ ══
pdf.section_title("5. Действия во время движения")
critical = [
"Пить воду каждые 2030 минут, даже если нет жажды. Жажда — уже признак обезвоживания.",
"Останавливаться каждые 1,52 часа на 1015 минут в тенистом месте. Не реже одного привала каждые 100 км.",
"При остановке — немедленно выйти из ТС, уйти в тень. Не оставаться в закрытой нагретой кабине.",
"Обтирать лицо и шею влажным полотенцем при каждой остановке.",
"Не закрывать окна наглухо — обеспечить сквозное проветривание кабины.",
"Не оставлять ТС под прямым солнцем — ставать в тень деревьев, зданий, навесов.",
]
for item in critical:
pdf.bullet(item, pdf.c["red"])
pdf.sub_title("Признаки теплового недомогания — немедленная остановка:")
symptoms = [
"Головная боль, головокружение, «мушки» перед глазами",
"Тошнота, рвота",
"Резкая слабость, сонливость,Difficulty concentrating",
"Учащённое сердцебиение, одышка без физической нагрузки",
"Покраснение лица, обильное потоотделение или его прекращение",
"Любые отклонения от нормального самочувствия",
]
for item in symptoms:
pdf.bullet(item, pdf.c["amber"])
pdf.callout_box(
"ПРИ ЛЮБЫХ ПРИЗНАКАХ ТЕПЛОВОГО НЕДОМОГАНИЯ:\n"
"1. Немедленно остановить ТС, выключить двигатель\n"
"2. Выйти из ТС, уйти в тень\n"
"3. Выпить 200300 мл воды мелкими глотками\n"
"4. Обтереть лицо, шею, запястья влажным полотенцем\n"
"5. При ухудшении — вызвать скорую помощь\n"
"6. Сообщить диспетчеру о невозможности продолжения рейса",
pdf.c["red"], (255, 240, 240),
)
# ══ 6. ОСОБЫЕ ТРЕБОВАНИЯ ══
pdf.section_title("6. Особые требования")
cards_special = [
("\u25b6", "Режим работы в жару",
"Выезд на линию \u2014 не ранее 6:00. Возвращение \u2014 не позднее 18:00. В период с 12:00 до 15:00 \u2014 обязательный перерыв в тенистом месте (не менее 30 минут)."),
("\u2716", "Запреты",
"Запрещается: оставлять детей в ТС, оставлять ТС с работающим двигателем без присмотра, перевозить легковоспламеняющиеся жидкости без охлаждения."),
("\u25cb", "Охлаждение",
"Допускается использование portative вентиляторов от прикуривателя, охлаждающих бандажей, бутылок с замороженной водой."),
("\u25a1", "Взаимный контроль",
"Водители обязаны проверять состояние здоровья коллег при встрече. При признаках недомогания у коллеги \u2014 оказать помощь и сообщить диспетчеру."),
]
y_cards6 = pdf.get_y()
cards_h6 = pdf.draw_cards_2x2(cards_special, y_cards6)
pdf.set_y(y_cards6 + cards_h6 + 4)
# ══ 7. ЧЕК-ЛИСТ ══
pdf.section_title("7. Чек-лист перед выездом")
checklist = [
"Состояние здоровья удовлетворительное",
"Взял запас воды (мин. 2 литра) в термосумке",
"Взял головной убор",
"Взял влажные салфетки / полотенце",
"Одежда лёгкая, светлая, из хлопка",
"Кабина ТС проветрена перед выездом",
"Окна открываются, вентиляция работает",
"Проверил уровень охлаждающей жидкости",
"Есть заряженный мобильный телефон",
"Знание маршрута и мест для привалов в тени",
]
for item in checklist:
pdf.set_font("DejaVu", "", 10)
pdf.set_text_color(*pdf.c["ink"])
pdf.set_x(pdf.l_margin + 1)
pdf.cell(6, 6, "\u2610")
pdf.set_x(pdf.l_margin + 8)
pdf.cell(0, 6, item, new_x="LMARGIN", new_y="NEXT")
pdf.ln(4)
# ══ 8. ЛИСТ ОЗНАКОМЛЕНИЯ ══
pdf.section_title("8. Лист ознакомления с инструктажем")
pdf.body_text(
"Своей подписью подтверждаю, что с содержанием инструктажа ознакомлен(а), "
"требования и меру ответственности осознаю, обязуюсь выполнять."
)
pdf.ln(4)
sig_header = ["Фамилия И.О.", "Должность / Категория", "Подпись и дата"]
sig_w = [55, 75, (pdf.w - pdf.l_margin - pdf.r_margin - 130)]
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()