feat / AEB-65 generate reports

This commit is contained in:
Timofey
2025-10-07 15:24:22 +03:00
parent 497cd7e292
commit 6e43a8afed
11 changed files with 289 additions and 33 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,143 @@
import csv
from io import StringIO, BytesIO
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from django.utils import timezone
import os
FONT_PATH = os.path.join(os.path.dirname(__file__), 'fonts')
ARIAL_PATH = os.path.join(FONT_PATH, 'arialmt.ttf')
ARIAL_BOLD_PATH = os.path.join(FONT_PATH, 'arial_bolditalicmt.ttf')
if not os.path.exists(ARIAL_PATH) or not os.path.exists(ARIAL_BOLD_PATH):
raise FileNotFoundError(
f"Шрифты не найдены. Пожалуйста, убедитесь что файлы arialmt.ttf и arial_bolditalicmt.ttf "
f"находятся в директории {FONT_PATH}"
)
pdfmetrics.registerFont(TTFont('Arial', ARIAL_PATH))
pdfmetrics.registerFont(TTFont('Arial-Bold', ARIAL_BOLD_PATH))
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
name='CustomNormal',
fontName='Arial',
fontSize=10,
leading=12,
))
styles.add(ParagraphStyle(
name='CustomHeading1',
fontName='Arial-Bold',
fontSize=14,
leading=16,
))
def generate_csv_report(alerts):
"""Генерирует CSV отчет из списка алертов"""
output = StringIO()
writer = csv.writer(output)
# заголовки
headers = ['ID', 'Датчик', 'Объект', 'Значение', 'Тип датчика', 'Сообщение',
'Уровень важности', 'Дата создания', 'Обработан']
writer.writerow(headers)
# данные
for alert in alerts:
zone = alert.sensor.zones.first()
object_name = zone.object.title if zone else 'Не указан'
writer.writerow([
alert.id,
alert.sensor.name,
object_name,
f"{alert.metric.value} {alert.sensor.signal_format.unit if alert.sensor.signal_format else ''}" if alert.metric.value else alert.metric.raw_value,
alert.sensor_type.name,
alert.message,
alert.severity,
timezone.localtime(alert.created_at).strftime("%Y-%m-%d %H:%M:%S %Z"),
'Да' if alert.resolved else 'Нет'
])
return output.getvalue()
def generate_pdf_report(alerts):
"""Генерирует PDF отчет из списка алертов"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=letter)
elements = []
# заголовок
title = Paragraph(
f"Отчет по алертам (сгенерирован {timezone.localtime().strftime('%Y-%m-%d %H:%M %Z')})",
styles['CustomHeading1']
)
elements.append(title)
# данные для таблицы
headers = ['ID', 'Датчик', 'Объект', 'Значение', 'Тип датчика', 'Сообщение',
'Уровень', 'Дата создания', 'Обработан']
# оборачиваем заголовки в параграф
header_cells = [Paragraph(str(header), styles['CustomNormal']) for header in headers]
data = [header_cells]
for alert in alerts:
zone = alert.sensor.zones.first()
object_name = zone.object.title if zone else 'Не указан'
# оборачиваем каждую ячейку в параграф
row_data = [
Paragraph(str(alert.id), styles['CustomNormal']),
Paragraph(str(alert.sensor.name), styles['CustomNormal']),
Paragraph(str(object_name), styles['CustomNormal']),
Paragraph(
str(f"{alert.metric.value} {alert.sensor.signal_format.unit if alert.sensor.signal_format else ''}" if alert.metric.value else alert.metric.raw_value),
styles['CustomNormal']
),
Paragraph(str(alert.sensor_type.name), styles['CustomNormal']),
Paragraph(str(alert.message), styles['CustomNormal']),
Paragraph(str(alert.severity), styles['CustomNormal']),
Paragraph(timezone.localtime(alert.created_at).strftime("%Y-%m-%d %H:%M:%S %Z"), styles['CustomNormal']),
Paragraph('Да' if alert.resolved else 'Нет', styles['CustomNormal'])
]
data.append(row_data)
# создаем таблицу
table = Table(data)
table.setStyle(TableStyle([
# стиль заголовка
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1b8755')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('TOPPADDING', (0, 0), (-1, 0), 12),
# стиль данных
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
('FONTSIZE', (0, 1), (-1, -1), 10),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('GRID', (0, 0), (-1, -1), 1, colors.black),
# отступы для всех ячеек
('LEFTPADDING', (0, 0), (-1, -1), 6),
('RIGHTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 1), (-1, -1), 8),
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
# чередующиеся цвета строк для лучшей читаемости
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
]))
elements.append(table)
doc.build(elements)
return buffer.getvalue()