diff --git a/tool/compare_report.docx b/tool/compare_report.docx new file mode 100644 index 0000000..26515c6 Binary files /dev/null and b/tool/compare_report.docx differ diff --git a/tool/compare_report.py b/tool/compare_report.py new file mode 100644 index 0000000..fb82ba0 --- /dev/null +++ b/tool/compare_report.py @@ -0,0 +1,574 @@ +import os +import random +from typing import List + +from docx import Document +from docx.enum.table import WD_ALIGN_VERTICAL +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.shared import Pt, RGBColor, Mm, Cm, Inches +from collections import defaultdict +from docxtpl import DocxTemplate, InlineImage, Listing, R, RichText +import jinja2 + + +# from tool.screen import * + + +class PartDetail1_1: + def __init__(self): + self.time = '?' + self.name = '?' + self.num = '?' + self.tp = '?' + self.type = '?' + self.stop_times = '?' + self.result = '?' + self.detail = '' + self.devail_value = [] + + +class PartDetail: + def __init__(self): + self.item1 = '?' + self.item2 = '?' + self.item3 = '?' + self.item4 = '?' + self.item5 = '?' + self.item6 = '?' + self.item7 = '?' + self.item8 = '-' + self.item9 = '?' + self.item10 = '?' + self.item11 = '?' + self.item12 = '?' + self.item13 = '?' + self.item14 = '?' + self.item15 = '?' + self.item16 = '?' + self.item17 = '?' + self.item18 = '?' + self.item19 = '?' + self.item20 = '?' + self.item21 = '?' + self.item22 = '?' + self.detail = '' + self.detail_value = [] + self.item_list = [] + self.table = [] + self.table_head = [] + self.head = [] + self.table1_head = [] + self.table2_head = [] + self.table1 = [] + self.table2 = [] + self.image = '' + self.images = [] + self.in_detail = '' + self.in_detail_value = [] + self.in_image = ImageDetail() + self.out_detail = '' + self.out_detail_value = [] + self.out_image = ImageDetail() + self.result = [] + self.optimize = [] + self.reason = [] + self.cut_image = ImageDetail() + + +class ImageDetail: + def __init__(self): + self.wave_id = '' + self.wave_name = '' + self.time_list = [] + self.tp = '' + self.high = 0 #高频停车路口,0否,1是 + self.odi = 0 #odi,0否,1是 + self.turn_ts = 0 #分流转向比,1正向,2反向 + self.in_ts = 0 #汇入转向比,1正向,2反向 + self.week_or_day = 0, #1日,2周 + self.date_name = '' #日:2024-10-15,周:2024-48 + self.tp_start = '' + self.tp_end = '' + + +class DocDocumentContrastCtx: + """路口优化对比报告""" + + def __init__(self, nodeid): + self.nodeid = nodeid + self.doc = DocxTemplate(self.get_template_file_path()) + self.title = None + self.cut_image = 0 + self.cut_image_userid = '15836903493' + self.cut_image_role = 'manager' + self.tpl_data = { + # 总体概述 + 'part1': self.tpl_paragraph_map['part1'], + 'part1_1': PartDetail(), + 'part1_key': 'part1', + # 优化前现状分析 + 'part2': self.tpl_paragraph_map['part2'], + 'part2_1': PartDetail(), # 用户传入 + 'part2_2': PartDetail(), # 用户传入 + 'part2_3': PartDetail(), # 用户传入 + 'part2_key': 'part2', + # 诊断问题 + 'part3': self.tpl_paragraph_map['part3'], + 'part3_1': PartDetail(), # 用户传入 + 'part3_key': 'part3', + # 优化方案 + 'part4': self.tpl_paragraph_map['part4'], + 'part4_1': PartDetail(), + 'part4_2': PartDetail(), + 'part4_key': 'part4', + # 优化后效果对比 + 'part5': self.tpl_paragraph_map['part5'], + 'part5_1': PartDetail(), + 'part5_key': 'part5', + } + + self.tpl_paragraph = { + 'part1': {'visible': 0}, + 'part2': {'visible': 0, 'part2_1': 0, 'part2_2': 0, 'part2_3': 0}, + 'part3': {'visible': 0}, + 'part4': {'visible': 0, 'part4_1': 0, 'part4_2': 0}, + 'part5': {'visible': 0} + } + return + + def get_template_file_path(self): + dir = os.path.dirname(os.path.abspath(__file__)) + return f"{dir}/compare_report.docx" + + def get_static_file_path(self): + dir = os.path.dirname(os.path.abspath(__file__)) + return f"{dir}/../temp" + + tpl_paragraph_map = { + 'part1': '总体概述', + 'part2': '优化前现状分析', + 'part2_1': '基本信息', + 'part2_2': '延误分析', + 'part2_3': '流量占比及分流转向比分析', + 'part3': '诊断问题', + 'part4': '优化方案', + 'part4_1': '改善措施', + 'part4_2': '配时方案', + 'part5': '优化后效果对比', + } + + def build_rich_text(self, detail, detail_value=None): + item_rt = RichText('') + item_details = detail.split('?') + for k, v in enumerate(item_details): + item_rt.add(f"{item_details[k]}") + if k != len(item_details) - 1: + item_rt.add(f"{detail_value[k]}", color="#FF0000", font=detail_value) + continue + return item_rt + + def build_rich_list(self, detail, detail_value): + result = [] + item_details = detail.split('?') + for k, v in enumerate(item_details): + item_rt = RichText('') + item_rt.add(f"{item_details[k]}", color="#000000", size=18) + result.append(item_rt) + if k != len(item_details) - 1: + item_rt = RichText('') + item_rt.add(f"{detail_value[k]}", color="#ff0000", size=18) + result.append(item_rt) + continue + return result + + def build_rich_list_bg(self, detail, detail_value): + result = [] + item_details = detail.split('?') + for k, v in enumerate(item_details): + item_rt = RichText('') + item_rt.add(f"{item_details[k]}", color="#000000", size=18, style="background-color: #FFFF00;") + result.append(item_rt) + if k != len(item_details) - 1: + item_rt = RichText('') + item_rt.add(f"{detail_value[k]}", color="#ff0000", size=18, style="background-color: #FFFF00;") + result.append(item_rt) + continue + return result + + def new_rich_text(self, text, color="#000000", font="仿宋_GB2312", size=None): + rt = RichText('') + if size: + rt.add(f"{text}", color=color, font=font, size=size) + if not size: + rt.add(f"{text}", color=color, font=font) + return rt + + def new_rich_text_list(self, texts): + rt_list = [] + for item_text in texts: + rt = RichText('') + rt.add(f"{item_text}", color="#ff0000") + rt_list.append(rt) + return rt_list + + def build_template(self, file_name): + context = {} + screen_image_list = [] + context['title'] = self.title + i = 0 + for part_key, value in self.tpl_data.items(): + match part_key: + case 'part1': + if self.tpl_paragraph[part_key]['visible'] == 0: + continue + i += 1 + table_sort = 0 + table_data = [] + context[part_key] = table_data + context['part1_key'] = str(i) + context['part1_name'] = value + item_data = { + 'part_name': '', + 'part_n': 'part1_1', + 'detail': [], + 'table_data': [], + } + item_data['detail'].append(self.tpl_data['part1_1'].item1) + item_data['detail'].append(self.tpl_data['part1_1'].item2) + item_data['detail'].append(self.tpl_data['part1_1'].item3) + item_data['detail'].append(self.tpl_data['part1_1'].item4) + # item_data['detail'].append(self.new_rich_text(self.tpl_data['part1_1'].item5, "#FF0000")) + # item_data['detail'].append(self.build_rich_text(self.tpl_data['part1_1'].detail, self.tpl_data['part1_1'].detail_value) if self.tpl_data['part1_1'].detail != '' else '') + if len(self.tpl_data['part1_1'].table) > 0: + for item_table in self.tpl_data['part1_1'].table: + item_data['table_data'].append(self.build_rich_text(item_table.detail, + item_table.detail_value) if item_table.detail != '' else '') + table_data.append(item_data) + + # for item_image in item_part_detail.images: + # image_sort += 1 + # if self.cut_image == 1: + # screen_image_list.append(CutReportImgColumn( + # wave_id=item_image.wave_id, + # wave_name=item_image.wave_name, + # time_list=item_image.time_list, + # tp_name=item_image.tp, + # high=item_image.high + # )) + # item_data_detail['images'].append({ + # 'sort': image_sort, + # 'name': item_image.wave_name, + # 'image': len(screen_image_list) - 1, + # }) + # item_data['detail'].append(item_data_detail) + # table_data.append(item_data) + case 'part2': + if self.tpl_paragraph[part_key]['visible'] == 0: + continue + i += 1 + table_sort = 0 + image_sort = 0 + table_data = [] + context[part_key] = table_data + context['part2_key'] = str(i) + context['part2_name'] = value + if self.tpl_paragraph[part_key]['part2_1'] == 1: + item_data = { + 'part_name': self.tpl_paragraph_map['part2_1'], + 'part_n': 'part2_1', + 'detail': [], + } + item_data['detail'].append(self.tpl_data['part2_1'].item1) + item_data['detail'].append(self.tpl_data['part2_1'].item2) + item_data['detail'].append(self.tpl_data['part2_1'].item3) + item_data['detail'].append(self.tpl_data['part2_1'].item4) + table_data.append(item_data) + if self.tpl_paragraph[part_key]['part2_2'] == 1: + item_data = { + 'part_name': self.tpl_paragraph_map['part2_2'], + 'part_n': 'part2_2', + 'detail': [], + } + for item in self.tpl_data['part2_2'].table: + item_data['detail'].append( + self.build_rich_text(item.detail, item.detail_value) if item.detail != '' else '') + table_data.append(item_data) + if self.tpl_paragraph[part_key]['part2_3'] == 1: + item_data = { + 'part_name': self.tpl_paragraph_map['part2_3'], + 'part_n': 'part2_3', + 'detail': [], + } + for item in self.tpl_data['part2_3'].table: + item_data['detail'].append( + self.build_rich_text(item.detail, item.detail_value) if item.detail != '' else '') + table_data.append(item_data) + case 'part3': + if self.tpl_paragraph[part_key]['visible'] == 0: + continue + i += 1 + table_data = [] + context[part_key] = table_data + context['part3_key'] = str(i) + context['part3_name'] = value + item_loop = 0 + loop = 0 + item_data = { + 'part_name': "", + 'part_n': 'part3_1', + 'detail': [], + } + for item_data_list in self.tpl_data['part3_1'].table: + item_data_detail = { + 'title': item_data_list.item1, + 'item_detail': [] + } + for item_data_table in item_data_list.table: + item_item_data_detail = { + 'title': item_data_table.item1, + 'item_detail': [] + } + for item_data_table_value in item_data_table.table: + item_item_data_detail['item_detail'].append( + self.build_rich_text(item_data_table_value.detail, + item_data_table_value.detail_value) if item_data_table_value.detail != '' else '') + item_data_detail['item_detail'].append(item_item_data_detail) + item_data['detail'].append(item_data_detail) + table_data.append(item_data) + case 'part4': + if self.tpl_paragraph[part_key]['visible'] == 0: + continue + i += 1 + table_sort = 0 + table_sort += 1 + table_data = [] + context[part_key] = table_data + context['part4_key'] = str(i) + context['part4_name'] = value + if self.tpl_paragraph[part_key]['part4_1'] == 1: + item_data = { + 'part_name': self.tpl_paragraph_map['part4_1'], + 'part_n': 'part4_1', + 'data': [], + } + table_data.append(item_data) + if self.tpl_paragraph[part_key]['part4_2'] == 1: + item_data = { + 'part_name': self.tpl_paragraph_map['part4_2'], + 'part_n': 'part4_1', + 'data': [], + } + table_data.append(item_data) + case 'part5': + if self.tpl_paragraph[part_key]['visible'] == 0: + continue + i += 1 + table_sort = 0 + table_sort += 1 + table_data = [] + context[part_key] = table_data + context['part5_key'] = str(i) + context['part5_name'] = value + item_data = { + 'part_name': "", + 'part_n': 'part5_1', + 'detail': '', + 'data': [], + } + item_data['detail'] = self.tpl_data['part5_1'].item1 + for item_table in self.tpl_data['part5_1'].table: + item_date_detail = { + 'title': item_table.item1, + 'item_detail': [] + } + for item_item_table in item_table.table: + item_date_detail['item_detail'].append(self.build_rich_text(item_item_table.detail, item_item_table.detail_value) if item_item_table.detail != '' else '') + item_data['data'].append(item_date_detail) + table_data.append(item_data) + + if len(screen_image_list) > 0: + cut_images, _ = cut_report_img(screen_image_list, self.cut_image_userid, self.cut_image_role) + if context.get('part1'): + for item_part in context['part1']: + if item_part['part_n'] == 'part1_3': + for item_detail in item_part['detail']: + if len(item_detail['images']) > 0: + for item_image in item_detail['images']: + item_image['image'] = InlineImage(self.doc, cut_images[item_image['image']], + width=Mm(146)) if cut_images[ + item_image['image']] else '' + self.doc.render(context, autoescape=True) + # # 渲染模板 + # for table in self.doc.tables: + # for row in table.rows: + # for cell in row.cells: + # for para in cell.paragraphs: + # set_font(para, para.text, '仿宋_GB2312', 30,font_color=(255, 0, 0), bg_color='FFFF00') + # for para in self.doc.paragraphs: + # set_font(para, para.text, '仿宋', 14) + self.doc.save(self.get_static_file_path() + f"/{file_name}.docx") + return f"/api/phase_template_download?nodeid={self.nodeid}&type=report&file_name={file_name}" + + +def set_font(para, text_to_find, font_name='仿宋', font_size=12, font_color=(0, 0, 0), bg_color='FFFFFF'): + for run in para.runs: + run.text = text_to_find + # if text_to_find in run.text: + run.font.name = font_name + run.font.size = Pt(font_size) + run.font.color.rgb = RGBColor(*font_color) + rPr = run._element.rPr + if rPr is None: + rPr = OxmlElement('w:rPr') + run._element.append(rPr) + rFonts = OxmlElement('w:rFonts') + rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}ascii', font_name) + rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}eastAsia', font_name) + rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}hAnsi', font_name) + rPr.append(rFonts) + shading_elm = OxmlElement('w:shd') + shading_elm.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}fill', bg_color) + pPr = para._element.find('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}pPr') + if pPr is None: + pPr = OxmlElement('w:pPr') + para._element.insert(0, pPr) + pPr.append(shading_elm) + + +def doc_demo_contrast(): + doc1 = DocDocumentContrastCtx('9660') #获取模版对象 + doc1.tpl_paragraph['part1']['visible'] = 1 + doc1.tpl_paragraph['part2']['visible'] = 1 + doc1.tpl_paragraph['part2']['part2_1'] = 1 + doc1.tpl_paragraph['part2']['part2_2'] = 1 + doc1.tpl_paragraph['part2']['part2_3'] = 1 + doc1.tpl_paragraph['part3']['visible'] = 1 + doc1.tpl_paragraph['part4']['visible'] = 1 + doc1.tpl_paragraph['part4']['part4_1'] = 1 + doc1.tpl_paragraph['part4']['part4_2'] = 1 + doc1.tpl_paragraph['part5']['visible'] = 1 + + doc1.cut_image = 0 + doc1.cut_image_userid = '15836903493' + doc1.cut_image_role = 'manager' + doc1.title = '清盛大道与龙江路交叉口' + + # =========1总体概论=========== + doc1.tpl_data['part1_1'].item1 = '2024年10月8日' + doc1.tpl_data['part1_1'].item2 = '2025年10月8日' + doc1.tpl_data['part1_1'].item3 = '清盛大道与龙江路交叉口' + doc1.tpl_data['part1_1'].item4 = '09:00-17:00' + + detail1 = PartDetail() + detail1.detail = "路口服务水平由?级提升为?级,提升?个等级;" + detail1.detail_value = ['B', 'A', '1'] + detail2 = PartDetail() + detail2.detail = "路口拥堵指数由?下降为?,减少?,优化率为?;" + detail2.detail_value = ['0.33', '0.56', '5.31', '3.21%'] + detail3 = PartDetail() + detail3.detail = "路口停车次数由?下降为?,减少?,优化率为?;" + detail3.detail_value = ['2.331', '0.563', '2.31', '6.21%'] + doc1.tpl_data['part1_1'].table = [detail1, detail2, detail3] + + #==========2.1优化前现状分析========== + doc1.tpl_data['part2_1'].item1 = '清盛大道与龙江路交叉口' + doc1.tpl_data['part2_1'].item2 = '东城区' + doc1.tpl_data['part2_1'].item3 = '主干路与次干路相交的十字路口' + doc1.tpl_data['part2_1'].item4 = '海信' + + #===========2.2延误分析============ + detail1 = PartDetail() + detail1.detail = "根据各进口道车辆延误时间分析,服务水平较低的进口道有?个。东南进口的延误时间为?s,西北进口的延误时间为?s。" + detail1.detail_value = ['3', '5', '7'] + detail2 = PartDetail() + detail2.detail = "根据各进口道拥堵指数分析,服务水平有?个。东南进口的延误时间为?s,西北进口的延误时间为?s。" + detail2.detail_value = ['2', '6', '9'] + doc1.tpl_data['part2_2'].table = [detail1, detail2] + + #===========2.3延误分析============ + detail1 = PartDetail() + detail1.detail = "根据车辆分流转向比分析,西南进口道流量占比最大为?,东南进口道流量占比最小为?。路口的主要车流方向为东北直行和西南直行,东北进口道以直行车流为主,占比为?;西南进口道以直行车流为主,占比为?。" + detail1.detail_value = ['5%', '7%', '8%', '12%'] + detail2 = PartDetail() + detail2.detail = "根据车辆分流转向比分析,西南进口道流量占比最大为?,东南进口道流量占比最小为?。路口的主要车流方向为东北直行和西南直行,东北进口道以直行车流为主,占比为?;西南进口道以直行车流为主,占比为?。" + detail2.detail_value = ['5%', '7%', '8%', '12%'] + doc1.tpl_data['part2_3'].table = [detail1, detail2] + + #==========3诊断问题============ + detail1_1 = PartDetail() + detail1_1.item1 = '多次排队' + detail1_1_1 = PartDetail() + detail1_1_1.detail = "西北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" + detail1_1_1.detail_value = ['16%'] + detail1_1_2 = PartDetail() + detail1_1_2.detail = "东北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" + detail1_1_2.detail_value = ['20%'] + detail1_1.table = [detail1_1_1, detail1_1_2] + detail1_2 = PartDetail() + detail1_2.item1 = '停车较多' + detail1_2_1 = PartDetail() + detail1_2_1.detail = "路口停车次数较高(2.1),整体通行效率不高。" + detail1_2_1.detail_value = ['2.1'] + detail1_2.table = [detail1_2_1] + detail1 = PartDetail() + detail1.item1 = '运行效果' + detail1.table = [detail1_1, detail1_2] + + detail2_1 = PartDetail() + detail2_1.item1 = '多次排队1' + detail2_1_1 = PartDetail() + detail2_1_1.detail = "西北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" + detail2_1_1.detail_value = ['16%'] + detail2_1_2 = PartDetail() + detail2_1_2.detail = "东北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" + detail2_1_2.detail_value = ['20%'] + detail2_1.table = [detail2_1_1, detail2_1_2] + detail2_2 = PartDetail() + detail2_2.item1 = '停车较多2' + detail2_2_1 = PartDetail() + detail2_2_1.detail = "路口停车次数较高(?),整体通行效率不高。" + detail2_2_1.detail_value = ['2.1'] + detail2_2.table = [detail2_2_1] + detail2 = PartDetail() + detail2.item1 = '均衡调控' + detail2.table = [detail1_1, detail1_2] + + doc1.tpl_data['part3_1'].table = [detail1, detail2] + + #==========4优化方案============= + + #==========5优化后效果对比========== + detail5_1_1 = PartDetail() + detail5_1_1.detail = '路口服务水平由?级提升为?级,提升?个等级;' + detail5_1_1.detail_value = ['A', 'B', '6'] + detail5_1_2 = PartDetail() + detail5_1_2.detail = '路口拥堵指数由?下降为?,减少?,优化率为?;' + detail5_1_2.detail_value = ['3', '2', '1','5%'] + detail5_1_3 = PartDetail() + detail5_1_3.detail = '路口停车次数由?下降为?,减少?,优化率为?;' + detail5_1_3.detail_value = ['3.13', '4.1334' , '3.13', '3.13%'] + detail5_1 = PartDetail() + detail5_1.item1 = '路口方面' + detail5_1.table = [detail5_1_1, detail5_1_2,detail5_1_3] + + detail5_2_1 = PartDetail() + detail5_2_1.detail = '路口服务水平由?级提升为?级,提升?个等级;' + detail5_2_1.detail_value = ['A', 'B', '6'] + detail5_2_2 = PartDetail() + detail5_2_2.detail = '路口拥堵指数由?下降为?,减少?,优化率为?;' + detail5_2_2.detail_value = ['3', '2', '1','5%'] + detail5_2_3 = PartDetail() + detail5_2_3.detail = '路口停车次数由?下降为?,减少?,优化率为?;' + detail5_2_3.detail_value = ['3.13', '4.1334' , '3.13', '3.13%'] + detail5_2 = PartDetail() + detail5_2.item1 = 'X进口方面' + detail5_2.table = [detail5_2_1, detail5_2_2,detail5_2_3] + + doc1.tpl_data['part5_1'].item1 = '方案下发后通过平台对路口优化效果进行对比,优化后路口及进口道效果提升较明显的指标如下:' + doc1.tpl_data['part5_1'].table = [detail5_1, detail5_2] + return doc1.build_template("对比报告") # 生成word + + +if __name__ == '__main__': + doc_demo_contrast()