clean unmatched summary layout
This commit is contained in:
@@ -42,3 +42,4 @@ V2:zip 解压后包含 `Patients_info.csv`,并按患者目录分别保存检
|
|||||||
“未检测到内容汇总”会收集标准字段全部为 `Not_Find`、但存在未匹配检测内容的记录,并按检测原因排序汇总。
|
“未检测到内容汇总”会收集标准字段全部为 `Not_Find`、但存在未匹配检测内容的记录,并按检测原因排序汇总。
|
||||||
|
|
||||||
未匹配检测内容会被规范化为独立列:表头为未匹配检测项目名,数据行仅保存对应检测值。
|
未匹配检测内容会被规范化为独立列:表头为未匹配检测项目名,数据行仅保存对应检测值。
|
||||||
|
如果某个工作表没有任何未匹配检测项目,则不会显示“未匹配检测内容”标识列。标准字段全为 `Not_Find` 且没有未匹配值的空结果行会被移除。
|
||||||
|
|||||||
@@ -222,12 +222,15 @@ def _render_sheet(sheet) -> str:
|
|||||||
table = '<div class="empty">\u6b64\u5de5\u4f5c\u8868\u6ca1\u6709\u53ef\u9884\u89c8\u7684\u6570\u636e\u3002</div>'
|
table = '<div class="empty">\u6b64\u5de5\u4f5c\u8868\u6ca1\u6709\u53ef\u9884\u89c8\u7684\u6570\u636e\u3002</div>'
|
||||||
if preview:
|
if preview:
|
||||||
max_cols = min(max((len(row) for row in preview), default=0), 18)
|
max_cols = min(max((len(row) for row in preview), default=0), 18)
|
||||||
|
unmatched_start = None
|
||||||
|
if preview and UNMATCHED in preview[0]:
|
||||||
|
unmatched_start = preview[0].index(UNMATCHED)
|
||||||
rows = []
|
rows = []
|
||||||
for index, row in enumerate(preview):
|
for index, row in enumerate(preview):
|
||||||
cells = []
|
cells = []
|
||||||
for value in (row + [""] * max_cols)[:max_cols]:
|
for col_index, value in enumerate((row + [""] * max_cols)[:max_cols]):
|
||||||
tag = "th" if index == 0 else "td"
|
tag = "th" if index == 0 else "td"
|
||||||
css_class = ' class="extra-col"' if index == 0 and value == UNMATCHED else ""
|
css_class = ' class="extra-col"' if unmatched_start is not None and col_index >= unmatched_start else ""
|
||||||
cells.append(f"<{tag}{css_class}>{html.escape(value)}</{tag}>")
|
cells.append(f"<{tag}{css_class}>{html.escape(value)}</{tag}>")
|
||||||
rows.append("<tr>" + "".join(cells) + "</tr>")
|
rows.append("<tr>" + "".join(cells) + "</tr>")
|
||||||
table = f'<div class="table-wrap"><table>{"".join(rows)}</table></div>'
|
table = f'<div class="table-wrap"><table>{"".join(rows)}</table></div>'
|
||||||
@@ -460,7 +463,7 @@ def _page_shell(body: str, subtitle: str = "\u4e0a\u4f20\u201c\u5f85\u5904\u7406
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}}
|
}}
|
||||||
th {{ background: #fbfcfd; font-weight: 700; }}
|
th {{ background: #fbfcfd; font-weight: 700; }}
|
||||||
th.extra-col {{ background: var(--extra); }}
|
th.extra-col, td.extra-col {{ background: var(--extra); }}
|
||||||
.empty {{
|
.empty {{
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
|
from openpyxl.styles import PatternFill
|
||||||
from openpyxl.styles import Font
|
from openpyxl.styles import Font
|
||||||
|
|
||||||
|
|
||||||
PROCESSOR_DIR = Path(__file__).resolve().parent / "processors"
|
PROCESSOR_DIR = Path(__file__).resolve().parent / "processors"
|
||||||
|
SUMMARY_SHEET_NAME = "\u672a\u68c0\u6d4b\u5230\u5185\u5bb9\u6c47\u603b"
|
||||||
|
UNMATCHED_HEADER = "\u672a\u5339\u914d\u68c0\u6d4b\u5185\u5bb9"
|
||||||
|
UNMATCHED_FILL = PatternFill(fill_type="solid", fgColor="FCE4D6")
|
||||||
|
|
||||||
|
|
||||||
class ProcessingError(Exception):
|
class ProcessingError(Exception):
|
||||||
@@ -342,6 +346,7 @@ def _postprocess_workbook(
|
|||||||
workbook = load_workbook(path)
|
workbook = load_workbook(path)
|
||||||
try:
|
try:
|
||||||
_normalize_unmatched_columns(workbook)
|
_normalize_unmatched_columns(workbook)
|
||||||
|
_remove_empty_not_find_rows(workbook)
|
||||||
summary_records = _collect_summary_records(workbook)
|
summary_records = _collect_summary_records(workbook)
|
||||||
|
|
||||||
if not include_unmatched_items:
|
if not include_unmatched_items:
|
||||||
@@ -352,11 +357,11 @@ def _postprocess_workbook(
|
|||||||
|
|
||||||
if not include_basic_sheets:
|
if not include_basic_sheets:
|
||||||
for sheet in list(workbook.worksheets):
|
for sheet in list(workbook.worksheets):
|
||||||
if sheet.title != "未检测到内容汇总":
|
if sheet.title != SUMMARY_SHEET_NAME:
|
||||||
workbook.remove(sheet)
|
workbook.remove(sheet)
|
||||||
|
|
||||||
if not workbook.worksheets:
|
if not workbook.worksheets:
|
||||||
workbook.create_sheet("未检测到内容汇总")
|
workbook.create_sheet(SUMMARY_SHEET_NAME)
|
||||||
|
|
||||||
workbook.save(path)
|
workbook.save(path)
|
||||||
finally:
|
finally:
|
||||||
@@ -365,11 +370,11 @@ def _postprocess_workbook(
|
|||||||
|
|
||||||
def _normalize_unmatched_columns(workbook) -> None:
|
def _normalize_unmatched_columns(workbook) -> None:
|
||||||
for sheet in workbook.worksheets:
|
for sheet in workbook.worksheets:
|
||||||
if sheet.title == "未检测到内容汇总" or sheet.max_row < 2:
|
if sheet.title == SUMMARY_SHEET_NAME or sheet.max_row < 2:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
||||||
unmatched_index = _find_header_index(header, "未匹配检测内容")
|
unmatched_index = _find_header_index(header, UNMATCHED_HEADER)
|
||||||
if unmatched_index is None:
|
if unmatched_index is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -390,26 +395,58 @@ def _normalize_unmatched_columns(workbook) -> None:
|
|||||||
if sheet.max_column >= marker_col:
|
if sheet.max_column >= marker_col:
|
||||||
sheet.delete_cols(marker_col, sheet.max_column - marker_col + 1)
|
sheet.delete_cols(marker_col, sheet.max_column - marker_col + 1)
|
||||||
|
|
||||||
sheet.cell(1, marker_col).value = "未匹配检测内容"
|
if not item_names:
|
||||||
|
sheet.delete_cols(marker_col, 1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
sheet.cell(1, marker_col).value = UNMATCHED_HEADER
|
||||||
sheet.cell(1, marker_col).font = Font(bold=True)
|
sheet.cell(1, marker_col).font = Font(bold=True)
|
||||||
|
sheet.cell(1, marker_col).fill = UNMATCHED_FILL
|
||||||
for offset, item_name in enumerate(item_names, start=1):
|
for offset, item_name in enumerate(item_names, start=1):
|
||||||
cell = sheet.cell(1, marker_col + offset)
|
cell = sheet.cell(1, marker_col + offset)
|
||||||
cell.value = item_name
|
cell.value = item_name
|
||||||
cell.font = Font(bold=True)
|
cell.font = Font(bold=True)
|
||||||
|
cell.fill = UNMATCHED_FILL
|
||||||
|
|
||||||
for row_index, parsed in enumerate(parsed_rows, start=2):
|
for row_index, parsed in enumerate(parsed_rows, start=2):
|
||||||
sheet.cell(row_index, marker_col).value = ""
|
sheet.cell(row_index, marker_col).value = ""
|
||||||
|
sheet.cell(row_index, marker_col).fill = UNMATCHED_FILL
|
||||||
for offset, item_name in enumerate(item_names, start=1):
|
for offset, item_name in enumerate(item_names, start=1):
|
||||||
sheet.cell(row_index, marker_col + offset).value = parsed.get(item_name, "")
|
cell = sheet.cell(row_index, marker_col + offset)
|
||||||
|
cell.value = parsed.get(item_name, "")
|
||||||
|
cell.fill = UNMATCHED_FILL
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_empty_not_find_rows(workbook) -> None:
|
||||||
|
for sheet in workbook.worksheets:
|
||||||
|
if sheet.title == SUMMARY_SHEET_NAME or sheet.max_row < 2:
|
||||||
|
continue
|
||||||
|
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
||||||
|
unmatched_index = _find_header_index(header, UNMATCHED_HEADER)
|
||||||
|
standard_end = unmatched_index if unmatched_index is not None else sheet.max_column
|
||||||
|
|
||||||
|
for row_index in range(sheet.max_row, 1, -1):
|
||||||
|
standard_values = [
|
||||||
|
_cell_text(sheet.cell(row_index, col).value)
|
||||||
|
for col in range(5, standard_end + 1)
|
||||||
|
]
|
||||||
|
unmatched_values = []
|
||||||
|
if unmatched_index is not None:
|
||||||
|
unmatched_values = [
|
||||||
|
_cell_text(sheet.cell(row_index, col).value)
|
||||||
|
for col in range(unmatched_index + 2, sheet.max_column + 1)
|
||||||
|
]
|
||||||
|
if _all_standard_values_missing(standard_values) and not any(unmatched_values):
|
||||||
|
sheet.delete_rows(row_index, 1)
|
||||||
|
|
||||||
|
|
||||||
def _collect_summary_records(workbook) -> list[dict[str, object]]:
|
def _collect_summary_records(workbook) -> list[dict[str, object]]:
|
||||||
records: list[dict[str, object]] = []
|
records: list[dict[str, object]] = []
|
||||||
for sheet in workbook.worksheets:
|
for sheet in workbook.worksheets:
|
||||||
if sheet.title == "未检测到内容汇总" or sheet.max_row < 2:
|
if sheet.title == SUMMARY_SHEET_NAME or sheet.max_row < 2:
|
||||||
continue
|
continue
|
||||||
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
||||||
unmatched_col = _find_header_index(header, "未匹配检测内容")
|
unmatched_col = _find_header_index(header, UNMATCHED_HEADER)
|
||||||
if unmatched_col is None:
|
if unmatched_col is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -445,11 +482,13 @@ def _collect_summary_records(workbook) -> list[dict[str, object]]:
|
|||||||
|
|
||||||
|
|
||||||
def _replace_summary_sheet(workbook, records: list[dict[str, object]]) -> None:
|
def _replace_summary_sheet(workbook, records: list[dict[str, object]]) -> None:
|
||||||
if "未检测到内容汇总" in workbook.sheetnames:
|
if SUMMARY_SHEET_NAME in workbook.sheetnames:
|
||||||
workbook.remove(workbook["未检测到内容汇总"])
|
workbook.remove(workbook[SUMMARY_SHEET_NAME])
|
||||||
summary = workbook.create_sheet("未检测到内容汇总", 0)
|
summary = workbook.create_sheet(SUMMARY_SHEET_NAME, 0)
|
||||||
if not records:
|
if not records:
|
||||||
summary.append(["姓名", "住院号", "采样时间", "检测原因"])
|
summary.append(["\u59d3\u540d", "\u4f4f\u9662\u53f7", "\u91c7\u6837\u65f6\u95f4", "\u68c0\u6d4b\u539f\u56e0"])
|
||||||
|
for cell in summary[summary.max_row]:
|
||||||
|
cell.font = Font(bold=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
records = sorted(records, key=lambda item: (_cell_text(item["reason"]), _cell_text(item["sample_time"])))
|
records = sorted(records, key=lambda item: (_cell_text(item["reason"]), _cell_text(item["sample_time"])))
|
||||||
@@ -467,8 +506,8 @@ def _replace_summary_sheet(workbook, records: list[dict[str, object]]) -> None:
|
|||||||
if item_name not in item_names:
|
if item_name not in item_names:
|
||||||
item_names.append(item_name)
|
item_names.append(item_name)
|
||||||
|
|
||||||
reason_label = f"检测原因(下方都是{reason}原因)" if reason else "检测原因"
|
reason_label = f"\u68c0\u6d4b\u539f\u56e0\uff08\u4e0b\u65b9\u90fd\u662f{reason}\u539f\u56e0\uff09" if reason else "\u68c0\u6d4b\u539f\u56e0"
|
||||||
header = ["姓名", "住院号", "采样时间", reason_label] + item_names
|
header = ["\u59d3\u540d", "\u4f4f\u9662\u53f7", "\u91c7\u6837\u65f6\u95f4", reason_label] + item_names
|
||||||
summary.append(header)
|
summary.append(header)
|
||||||
for cell in summary[summary.max_row]:
|
for cell in summary[summary.max_row]:
|
||||||
cell.font = Font(bold=True)
|
cell.font = Font(bold=True)
|
||||||
@@ -484,7 +523,6 @@ def _replace_summary_sheet(workbook, records: list[dict[str, object]]) -> None:
|
|||||||
]
|
]
|
||||||
+ [item_values.get(item_name, "") for item_name in item_names]
|
+ [item_values.get(item_name, "") for item_name in item_names]
|
||||||
)
|
)
|
||||||
summary.append([])
|
|
||||||
|
|
||||||
for column_cells in summary.columns:
|
for column_cells in summary.columns:
|
||||||
max_length = max(len(_cell_text(cell.value)) for cell in column_cells)
|
max_length = max(len(_cell_text(cell.value)) for cell in column_cells)
|
||||||
@@ -493,10 +531,10 @@ def _replace_summary_sheet(workbook, records: list[dict[str, object]]) -> None:
|
|||||||
|
|
||||||
def _remove_unmatched_columns(workbook) -> None:
|
def _remove_unmatched_columns(workbook) -> None:
|
||||||
for sheet in workbook.worksheets:
|
for sheet in workbook.worksheets:
|
||||||
if sheet.title == "未检测到内容汇总" or sheet.max_row < 1:
|
if sheet.title == SUMMARY_SHEET_NAME or sheet.max_row < 1:
|
||||||
continue
|
continue
|
||||||
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
header = [_cell_text(sheet.cell(1, col).value) for col in range(1, sheet.max_column + 1)]
|
||||||
unmatched_col = _find_header_index(header, "未匹配检测内容")
|
unmatched_col = _find_header_index(header, UNMATCHED_HEADER)
|
||||||
if unmatched_col is not None:
|
if unmatched_col is not None:
|
||||||
sheet.delete_cols(unmatched_col + 1, sheet.max_column - unmatched_col)
|
sheet.delete_cols(unmatched_col + 1, sheet.max_column - unmatched_col)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user