Compare commits

...

11 Commits

Author SHA1 Message Date
许标 1daa07176e feat 🐛:1 6 months ago
许标 0535bda51a feat 🐛:add 7-29 6 months ago
许标 6a8c682727 feat 🐛:add replace 7 months ago
许标 e73e10d62d feat 🐛:add 7 months ago
许标 1b82ffe6c8 feat 🐛: add ceshi 7 months ago
许标 353b0b237f feat 🐛:add old 7 months ago
许标 807067659f feat 🐛:add 7 months ago
许标 6bcc43891b feat 🐛:add upgrade 7 months ago
许标 d99b1aa51c feat 🐛:add cood 7 months ago
许标 44d3f72ff3 feat 🐛:add templetemt 7 months ago
许标 de1839b27d feat 🐛:add before 7 months ago

@ -136,7 +136,7 @@ async def upload_file(files: List[UploadFile] = File(...)):
summary="生成日报",
description="生成日报,将生成的简报和日报文档转成html返回前端",
)
async def generate_report(background_tasks: BackgroundTasks, time_type: int = 0):
async def generate_report(background_tasks: BackgroundTasks, time_type: int = 0,over_load_value: int =0):
global data_dict
try:
logger.info("开始生成日报")
@ -148,8 +148,10 @@ async def generate_report(background_tasks: BackgroundTasks, time_type: int = 0)
# 存储文件的路径
fold_path = str(Path(UPLOAD_DIR).resolve()).replace("\\", "/")
data_dict = deal_docx(fold_path, DOWNLOAD_DIR,time_type=time_type)
data_dict = deal_docx(fold_path, DOWNLOAD_DIR,time_type=time_type,over_load_value=over_load_value)
if "status" in data_dict:
if not data_dict["status"]:
return data_dict
# 判断是否生成日报成功如果成功则转成html返回前端
report_sim_html = docx2html(data_dict["daily_repo_simple"])
report_html = docx2html(data_dict["daily_report"])
@ -158,7 +160,7 @@ async def generate_report(background_tasks: BackgroundTasks, time_type: int = 0)
# 将数据写入数据库
save_word_document(data_dict)
logger.info("日报生成返回成果")
# 返回 JSON 包含 HTML 内容
return JSONResponse(
content={
@ -181,7 +183,7 @@ async def generate_report(background_tasks: BackgroundTasks, time_type: int = 0)
# 将原始数据保存到数据库
finally:
try:
if os.listdir(UPLOAD_DIR):
if os.listdir(UPLOAD_DIR) and "save_folder" in data_dict:
raw_data_path = move_raw_files(
UPLOAD_DIR, DOWNLOAD_RAW_DIR, data_dict["save_folder"]
)

Binary file not shown.

Binary file not shown.

@ -1,5 +1,8 @@
import re
from numpy.ma.core import floor
# text = "6月15日17时至6月16日17时期间全网累计停电132.59万户次5分钟以内短时停电用户23.48万户次环比减少68.28万户次其中重要用户停电0户次用户停电情况总体平稳。"
@ -12,14 +15,16 @@ def count_change_outage(text):
total_outage = float(matches[0]) # 累计停电用户数
short_term_outage = float(matches[1]) # 短时停电用户数
change_outage = float(matches[2]) # 环比变化用户数
total_yesterday =0
# 判断是增加还是减少
if "减少" in text:
result = change_outage / (total_outage + change_outage)
type = "减少"
total_yesterday = total_outage + change_outage
elif "增加" in text:
result = change_outage / (total_outage - change_outage)
type = "增加"
total_yesterday = total_outage - change_outage
else:
result = None # 或者其他默认值
@ -42,7 +47,7 @@ def count_change_outage(text):
else:
important_stop_outage = "0"
print("未找到重要用户停电户次")
total_yesterday_formatted = round(total_yesterday, 2)
return (
total_outage,
short_term_outage,
@ -51,6 +56,7 @@ def count_change_outage(text):
short_percentage,
important_stop_outage,
type,
total_yesterday_formatted
)

@ -148,6 +148,78 @@ def deal_excel(start_time, end_time, file_path):
except Exception as e:
logger.exception(f"对数据按照’省‘进行分类汇总{e}")
def deal_excel_over_load(file_path):
try:
logger.info("开始分析配变过载excel")
# 获取所有sheet页名称
excel_file = pd.ExcelFile(file_path)
sheet_names = excel_file.sheet_names
pattern_sheet = r"重过载明细"
# 使用正则表达式进行模糊匹配(不区分大小写)
matched_sheets = [
sheet
for sheet in sheet_names
if re.fullmatch(pattern_sheet, sheet, re.IGNORECASE)
]
if len(matched_sheets) == 1:
final_sheet = matched_sheets[0]
else:
logger.error("没有找到匹配的sheet页")
return None
df = pd.read_excel(
file_path,
sheet_name=final_sheet,
)
values_to_include = ['严重过载', '一般过载']
filtered_df = df[df['重过载情况'].isin(values_to_include)]
grouped_df = filtered_df.groupby(['分子公司', '地市局']).size().reset_index(name='记录数')
sorted_df = grouped_df.sort_values(by='记录数', ascending=False)
#top_5_results = sorted_df.head(5)
# ---------------------------------------去点中间或两侧空格---------------------------------
sorted_df["分子公司"] = sorted_df["分子公司"].str.strip().str.replace(r"\s+", "", regex=True)
sorted_df["地市局"] = sorted_df["地市局"].str.strip().str.replace(r"\s+", "", regex=True)
# ---------------------------------------去点中间或两侧空格---------------------------------
sorted_df['公司地市'] = sorted_df['分子公司'] + sorted_df['地市局']
#top_5_results.loc[:, '公司地市'] = top_5_results['分子公司'] + top_5_results['地市局']
# 最终选择需要返回的列:'公司_地市' 和 '记录数'
final_output = sorted_df[['公司地市', '记录数']].values.tolist()
overload_top5_list = []
if final_output :
need_final_output = (
final_output[0:5]
if len(final_output) > 5
else final_output
)
other_final_output = (
final_output[5:] if len(final_output) > 5 else []
)
other_count = 0
if (
len(other_final_output) > 0
and final_output[4][1] == final_output[5][1]
):
for i in range(len(other_final_output)):
if other_final_output[i][1] == final_output[4][1]:
other_count += 1
overload_value = need_final_output[len(need_final_output) - 1][1]
count = 0
for i in range(len(need_final_output)):
current_final_output = need_final_output[i][1]
if current_final_output == overload_value and other_count >0:
count += 1
else:
overload_top5_list.append(need_final_output[i])
if count >0 :
overload_top5_list.append([f"其他{count + other_count}家单位",overload_value])
return overload_top5_list
except Exception as e:
logger.exception(f"对数据按照’省‘进行分类汇总{e}")
# 判断地市top5环比方法
def top5_dod_analysis(top, district_stat_before):

@ -43,7 +43,7 @@ def plot_electricity_comparison(year, month, day, data):
valid_data = {}
for title, values in data.items():
if "昨天" in values and "今天" in values:
if values["昨天"] is not None and values["今天"] is not None:
if values["昨天"] is not None and values["今天"] is not None and values["昨天"]!="" and values["今天"]!="":
valid_data[title] = values
# 如果没有有效的数据,返回 None 或其他指示

@ -8,12 +8,12 @@ from fastapi import HTTPException
from fastapi.responses import JSONResponse
from docx import Document
from pathlib import Path
from datetime import datetime
from datetime import datetime, date
from docx.shared import Inches, Pt
from app.tools.deal_excels import deal_excel, top5_dod_analysis, transform_data
from app.tools.deal_excels import deal_excel, top5_dod_analysis, transform_data, deal_excel_over_load
from app.tools.get_time import get_time
from app.tools.replace_text import replace_text_in_docx
from app.tools.replace_table import copy_table, copy_sta_table, copy_table_no_align
from app.tools.replace_table import copy_table, copy_sta_table
from app.tools.style import table_style
from app.tools.effective_date import get_next_day
from app.tools.find_before_word import extract_overload_info_from_previous_day
@ -24,7 +24,7 @@ from app.tools.draw_picture import plot_electricity_comparison
logger = logging.getLogger(__name__)
def deal_docx(folder_path, save_path=None, time_type=0):
def deal_docx(folder_path, save_path=None, time_type=0,over_load_value=0):
"""
:param folder_path: 文件上传后保存的路径
:param save_path: 最终生成的日报/简报的保存路径
@ -61,8 +61,12 @@ def deal_docx(folder_path, save_path=None, time_type=0):
file_path_dict["power_off_excel"] = folder_path + "/" + file
continue
if file.endswith(".xlsx") and "停电报表配变明细" in file:
file_path_dict["over_load_excel"] = folder_path + "/" + file
continue
# 如果传入的文件不对,抛出异常
if len(file_path_dict) != 3:
if len(file_path_dict) != 4:
logger.exception("文件格式错误")
raise HTTPException(
status_code=400,
@ -104,13 +108,29 @@ def deal_docx(folder_path, save_path=None, time_type=0):
)
continue
if "first_point_para2" not in doc_dict:
if re.match(r".*抢修工单.*", para.text):
# print(para.text)
doc_dict["first_point_para2"] = re.sub(
r"[,]整体抢修工作态势正常。+$", "", para.text
)
continue
# 第二点过载台数
if re.search(r"过载(\d+)(?:[条台]|\s*)?", para.text):
doc_dict["over_load"] = (
re.search(r"过载(\d+)(?:[条台]|\s*)?", para.text).group().replace("过载", "")
).replace("", "").replace("", "")
# print(doc_dict['over_load'])
continue
"""
if re.search(r"过载\d+台", para.text):
doc_dict["over_load"] = (
re.search(r"过载\d+台", para.text).group().replace("过载", "")
).replace("", "")
# print(doc_dict['over_load'])
continue
"""
# 拿到舆情的段落
paragraphs_sentiment = doc_sentiment.paragraphs
@ -137,11 +157,12 @@ def deal_docx(folder_path, save_path=None, time_type=0):
table1 = tables[0]
# 表2配变过载监测汇总表
table2 = tables[1]
# 表2配变过载监测汇总表——配变过载集成到了一张表table1
#table2 = tables[1]
# 表3停电用户前五供电局
table3 = tables[2]
# 表3停电用户前五供电局——停电前五移动到了第二张表
table3 = tables[1]
#table3 = tables[2]
# 新增表4 95598供电类投诉前五供电局统计表
# table4 = doc_poweroff.add_table(6, 5)
@ -251,7 +272,7 @@ def deal_docx(folder_path, save_path=None, time_type=0):
count = 0
for i in range(len(need_district_statistics)):
current_poweroff_value = need_district_statistics[i][1]
if current_poweroff_value == poweroff_value:
if current_poweroff_value == poweroff_value and other_count >0:
count += 1
else:
top5_name_list.append(need_district_statistics[i][0])
@ -259,6 +280,11 @@ def deal_docx(folder_path, save_path=None, time_type=0):
top_dod_dict[need_district_statistics[i][0]] = top5_dod_analysis(
need_district_statistics[i], district_stat_before
)
if count >0 :
top5_name_list.append(f"其他{count + other_count}家单位")
top5_poweroff_list.append(poweroff_value)
top_dod_dict["其他单位"] = ""
"""
if count == 1 and other_count==0:
top5_name_list.append(
need_district_statistics[len(need_district_statistics) - 1][0]
@ -276,7 +302,7 @@ def deal_docx(folder_path, save_path=None, time_type=0):
top5_name_list.append(f"其他{count + other_count}家单位")
top5_poweroff_list.append(poweroff_value)
top_dod_dict["其他单位"] = ""
"""
# old_version
"""
if len(district_statistics) >= 5:
@ -348,10 +374,10 @@ def deal_docx(folder_path, save_path=None, time_type=0):
standardize_date = (
f"{month_before}{day_before}日17时至{month}{day}日17时期间"
)
elif time_type == 1:
# -------------------------20250429更新修改开始和结束时间---------------------------------
standardize_date = f"{year}{month}{day}日0时至24时期间"
#standardize_date = f"{year}年{month}月{day}日0时至24时期间"
standardize_date = f"{month}{day}日0时至24时期间"
# standardize_date = ''
# -------------------------20250429更新修改开始和结束时间---------------------------------
@ -408,6 +434,7 @@ def deal_docx(folder_path, save_path=None, time_type=0):
short_precentage,
important_stop_outage,
type,
total_yesterday
) = count_change_outage(doc_dict["have_important"])
# 获取舆情数字信息
today_sentiment, type_sentiment, yesterday_sentiment, result_sentiment = (
@ -424,11 +451,22 @@ def deal_docx(folder_path, save_path=None, time_type=0):
current_doc_name = f"南方电网公司停电抢修投诉服务舆情管控三工单联动监测日报{year}{int(month):02d}{int(day):02d}.docx"
if "over_load" not in doc_dict:
raise Exception("过载数据不符合标准未解析,请查看文档")
doc_dict_over_load = doc_dict["over_load"]
over_load_before = extract_overload_info_from_previous_day(
current_word=current_doc_name
)
# 2025-07-21
if not over_load_before :
if over_load_value>0:
over_load_before = over_load_value
else:
print("缺少昨日配变过载数量,需要手动填写")
return {
"status":False,
"error_message":"缺少昨日配变过载数量,需要手动填写"
}
if over_load_before:
# 将字符串转换为浮点数
over_load_before = float(over_load_before)
@ -483,10 +521,9 @@ def deal_docx(folder_path, save_path=None, time_type=0):
# 替换模板字符串
replace_text_in_docx(electricity_daily_simple, replacements_simple)
datas = {
"停电用户\n(万户)": {
"昨天": total_outage + change_outage,
"昨天": total_yesterday,
"今天": total_outage,
},
"过载配变\n(台)": {"昨天": over_load_before, "今天": doc_dict_over_load},
@ -503,12 +540,14 @@ def deal_docx(folder_path, save_path=None, time_type=0):
# 将数据转换为DataFrame
df = pd.DataFrame(datas)
# 遍历 datas 中的每个值,将 None 或 空字符串替换为 0
"""
for key, value in datas.items():
for sub_key, sub_value in value.items():
if sub_value is None or sub_value == "":
datas[key][sub_key] = None # 将不存在或为空的值设置为 0
else:
datas[key][sub_key] = int(sub_value) # 确保值是整数
"""
# 生成柱状图
img_path = plot_electricity_comparison(year, month, day, datas)
@ -577,7 +616,8 @@ def deal_docx(folder_path, save_path=None, time_type=0):
# 将数据组装相关的时间内容
doc_dict["first_point_para1"] = standardize_date + doc_dict["first_point_para1"]
doc_dict["sentiment_para"] = standardize_date + doc_dict["sentiment_para"]
#doc_dict["sentiment_para"] = standardize_date + doc_dict["sentiment_para"]
doc_dict["sentiment_para"] = doc_dict["sentiment_para"]
# {{standardize_date}}全网收到{{complain_num}}条供电类投诉,环比{{complain_dod}}条;
complain_text = (
@ -588,6 +628,7 @@ def deal_docx(folder_path, save_path=None, time_type=0):
replacements = {}
if time_type == 0:
# 组装替换的文本
day_cycle = getDayCycle(year,month,day)
replacements = {
"{{year}}": year,
"{{month}}": month,
@ -601,10 +642,12 @@ def deal_docx(folder_path, save_path=None, time_type=0):
"{{sentiment}}": doc_dict["sentiment_para"],
"{{sentiment_trend}}": doc_dict["sentiment_trend"],
"{{exception}}": electricity_exception,
"{{period}}": str(day_cycle),
}
elif time_type == 1:
# 组装替换的文本
day_cycle = getDayCycle(year_now,month_now,day_now)
replacements = {
"{{year}}": str(year_now),
"{{month}}": str(month_now),
@ -618,6 +661,7 @@ def deal_docx(folder_path, save_path=None, time_type=0):
"{{sentiment}}": doc_dict["sentiment_para"],
"{{sentiment_trend}}": doc_dict["sentiment_trend"],
"{{exception}}": electricity_exception,
"{{period}}": str(day_cycle),
}
# 组装日报
@ -678,21 +722,20 @@ def deal_docx(folder_path, save_path=None, time_type=0):
# 复制表1的数据
logger.info("将配变过载写入表0第二行开始第9列开始")
# 定义要查看的区域范围
start_row2 = 1 # 起始行索引从0开始
end_row2 = 7 # 结束行索引(不包括)
start_col2 = 2 # 起始列索引从0开始
end_col2 = 4 # 结束列索引(不包括)
target_row=1
target_col=7
copy_table_no_align(
table2,
start_row2 = 2 # 起始行索引从0开始
end_row2 = 8 # 结束行索引(不包括)
start_col2 = 9 # 起始列索引从0开始
end_col2 = 11 # 结束列索引(不包括)
#target_row=1
#target_col=7
copy_table(
table1,
electricity_daily.tables[0],
start_row2,
end_row2,
start_col2,
end_col2,
target_row,
target_col
0
)
"""
copy_table(
@ -748,6 +791,21 @@ def deal_docx(folder_path, save_path=None, time_type=0):
is_dynamics=False
)
logger.info("将配变重过载的数据插入表格5")
# 统计配变过载前五的数据
top_5_results = deal_excel_over_load(file_path_dict["over_load_excel"])
# 表4中的插入位置
start_tb4_row = 2
start_tb4_col = 8
if top_5_results:
#data = top_5_results.values
copy_sta_table(
electricity_daily.tables[1],
top_5_results,
start_tb4_row,
start_tb4_col,
)
# copy_sta_table(electricity_daily.tables[3], top5_list, start_tb4_row, start_tb4_col)
# 将表格中的字体中文设置成仿宋英文数字设置成新罗马均为11号大小
@ -821,6 +879,17 @@ def delete_old_file(file):
logger.info(f"删除旧文件失败:{e}")
def getDayCycle(year_str,month_str,day_str):
year = int(year_str)
month = int(month_str)
day = int(day_str)
# 创建日期对象
d = date(year, month, day)
# 获取是今年的第几天
day_of_year = d.timetuple().tm_yday-1
print(f"{year}{month}{day}日是今年的第 {day_of_year} 天。")
return day_of_year
# if __name__ == '__main__':
# folder_path = 'E:/work_data/work/三工单日报/20250310/20250310'
#

@ -64,6 +64,13 @@ def extract_overload_info_from_previous_day(
if file_name == previous_word_name:
previous_word_path = os.path.join(previous_folder_path, file_name)
break
pattern = r"(日报)(\d{4})(\d{2})"
match = re.search(pattern, previous_word_name)
original_year_part = match.group(2)
new_filename = previous_word_name.replace(original_year_part, "-", 1)
if file_name == new_filename:
previous_word_path = os.path.join(previous_folder_path, file_name)
break
if not previous_word_path:
logger.error(f"前一天的 Word 文档不存在:{previous_word_name}")
@ -75,12 +82,13 @@ def extract_overload_info_from_previous_day(
# 提取过载台数信息
overload_info = None
for para in doc.paragraphs:
if re.search(r"过载\d+", para.text):
if re.search(r"过载(\d+)(?:[条]|\s*)?", para.text):
overload_info = (
re.search(r"过载\d+", para.text)
re.search(r"过载(\d+)(?:[条]|\s*)?", para.text)
.group()
.replace("过载", "")
.replace("", "")
.replace("", "")
)
break

@ -1,4 +1,5 @@
import logging
from multiprocessing.spawn import old_main_modules
from docx.oxml.ns import qn
from docx.shared import Pt
@ -8,11 +9,57 @@ logger = logging.getLogger(__name__)
#excluded_list = ['{{year}}','{{month}}', '{{day}}']
# 将文档中的字符串变量替换成提取内容
"""
def replace_text_in_paragraph(paragraph, old_text, new_text):
try:
runs = list(paragraph.runs) # 先复制一份,避免边遍历边修改
for run in runs:
if old_text in run.text:
# 1. 拆分 run把 old_text 前后拆开
before, old, after = run.text.partition(old_text)
run.clear()
paragraph.add_run(before)
target_run = paragraph.add_run(new_text)
paragraph.add_run(after)
# 3. 仅给目标 run 加粗 + 字体
if old_text == "{{sentiment_trend}}":
target_run.bold = True
target_run.font.name = "Times New Roman"
target_run._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
target_run.font.size = Pt(16)
elif old_text.startswith('{{') and old_text.endswith('}}'):
# 其它模板只改字体、字号,不加粗
target_run.font.name = "Times New Roman"
target_run._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
target_run.font.size = Pt(16)
except Exception as e:
logger.exception(f"替换段落里的文本失败:{e}")
print(f"替换段落里的文本失败:{e}")
"""
def replace_text_in_paragraph(paragraph, old_text, new_text):
try:
if old_text in paragraph.text: # 检查段落中是否存在模板字符串
# 遍历段落的每个运行
for run in paragraph.runs:
if old_text =="{{sentiment_trend}}":
before_text, matched_text, after_text = run.text.partition(old_text)
run.clear()
before_run = paragraph.add_run(before_text)
target_run = paragraph.add_run(new_text)
print("加黑")
target_run.bold = True
target_run.font.name = "Times New Roman"
target_run._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
target_run.font.size = Pt(16)
before_run.font.name = "Times New Roman"
before_run._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
before_run.font.size = Pt(16)
after_run = paragraph.add_run(after_text)
after_run.font.name = "Times New Roman"
after_run._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
after_run.font.size = Pt(16)
else:
if old_text in run.text:
run.text = run.text.replace(old_text, new_text)
if old_text.startswith('{{') :

@ -1,6 +1,7 @@
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.shared import Pt
from docx.oxml.ns import qn
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
import logging
@ -16,8 +17,12 @@ def table_style(table):
# 遍历每一行的每个单元格
for cell in row.cells:
# 遍历单元格的每个段落
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
for paragraph in cell.paragraphs:
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
paragraph.paragraph_format.line_spacing = Pt(11)
paragraph.paragraph_format.space_after = Pt(0)
# 遍历段落的每个运行Run
for run in paragraph.runs:
# 设置英文字体(适用于数字和英文)

@ -2,3 +2,29 @@
1) 日报模板
2) tools/final_doc.py
3) tools/replace_table.py
4) tools/find_before_word.py
# 2025-07-15
1) 日报模板
2) tools/final_doc.py
3) tools/replace_table.py
4) tools/find_before_word.py
5) tools/deal_excels.py
# 2025-07-23
1) router.py
2) 日报模板
3) tools/deal_excels.py
4) tools/final_doc.py
5) tools/replace_text.py
6) tools/style.py
# 2025-07-29
1) tools/final_doc.py
2) tools/deal_excels.py
3) tools/count_data.py
# 2025-08-04
1) tools/deal_excels.py
2) tools/final_doc.py
3) 日报模块

Loading…
Cancel
Save