diff --git a/README.md b/README.md index 4765d7c..b8cb27d 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,4 @@ V2:zip 解压后包含 `Patients_info.csv`,并按患者目录分别保存检 “未检测到内容汇总”会收集标准字段全部为 `Not_Find`、但存在未匹配检测内容的记录,并按检测原因排序汇总。 未匹配检测内容会被规范化为独立列:表头为未匹配检测项目名,数据行仅保存对应检测值。 +如果某个工作表没有任何未匹配检测项目,则不会显示“未匹配检测内容”标识列。标准字段全为 `Not_Find` 且没有未匹配值的空结果行会被移除。 diff --git a/app/main.py b/app/main.py index 7f5d340..36c393f 100644 --- a/app/main.py +++ b/app/main.py @@ -222,12 +222,15 @@ def _render_sheet(sheet) -> str: table = '
\u6b64\u5de5\u4f5c\u8868\u6ca1\u6709\u53ef\u9884\u89c8\u7684\u6570\u636e\u3002
' if preview: 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 = [] for index, row in enumerate(preview): 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" - 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)}") rows.append("" + "".join(cells) + "") table = f'
{"".join(rows)}
' @@ -460,7 +463,7 @@ def _page_shell(body: str, subtitle: str = "\u4e0a\u4f20\u201c\u5f85\u5904\u7406 white-space: nowrap; }} th {{ background: #fbfcfd; font-weight: 700; }} - th.extra-col {{ background: var(--extra); }} + th.extra-col, td.extra-col {{ background: var(--extra); }} .empty {{ color: var(--muted); font-size: 13px; diff --git a/app/processor.py b/app/processor.py index 91df4df..93cf155 100644 --- a/app/processor.py +++ b/app/processor.py @@ -8,10 +8,14 @@ from dataclasses import dataclass from pathlib import Path from openpyxl import load_workbook +from openpyxl.styles import PatternFill from openpyxl.styles import Font 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): @@ -342,6 +346,7 @@ def _postprocess_workbook( workbook = load_workbook(path) try: _normalize_unmatched_columns(workbook) + _remove_empty_not_find_rows(workbook) summary_records = _collect_summary_records(workbook) if not include_unmatched_items: @@ -352,11 +357,11 @@ def _postprocess_workbook( if not include_basic_sheets: for sheet in list(workbook.worksheets): - if sheet.title != "未检测到内容汇总": + if sheet.title != SUMMARY_SHEET_NAME: workbook.remove(sheet) if not workbook.worksheets: - workbook.create_sheet("未检测到内容汇总") + workbook.create_sheet(SUMMARY_SHEET_NAME) workbook.save(path) finally: @@ -365,11 +370,11 @@ def _postprocess_workbook( def _normalize_unmatched_columns(workbook) -> None: 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 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: continue @@ -390,26 +395,58 @@ def _normalize_unmatched_columns(workbook) -> None: if sheet.max_column >= marker_col: 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).fill = UNMATCHED_FILL for offset, item_name in enumerate(item_names, start=1): cell = sheet.cell(1, marker_col + offset) cell.value = item_name cell.font = Font(bold=True) + cell.fill = UNMATCHED_FILL for row_index, parsed in enumerate(parsed_rows, start=2): 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): - 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]]: records: list[dict[str, object]] = [] 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 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: 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: - if "未检测到内容汇总" in workbook.sheetnames: - workbook.remove(workbook["未检测到内容汇总"]) - summary = workbook.create_sheet("未检测到内容汇总", 0) + if SUMMARY_SHEET_NAME in workbook.sheetnames: + workbook.remove(workbook[SUMMARY_SHEET_NAME]) + summary = workbook.create_sheet(SUMMARY_SHEET_NAME, 0) 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 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: item_names.append(item_name) - reason_label = f"检测原因(下方都是{reason}原因)" if reason else "检测原因" - header = ["姓名", "住院号", "采样时间", reason_label] + item_names + reason_label = f"\u68c0\u6d4b\u539f\u56e0\uff08\u4e0b\u65b9\u90fd\u662f{reason}\u539f\u56e0\uff09" if reason else "\u68c0\u6d4b\u539f\u56e0" + header = ["\u59d3\u540d", "\u4f4f\u9662\u53f7", "\u91c7\u6837\u65f6\u95f4", reason_label] + item_names summary.append(header) for cell in summary[summary.max_row]: 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] ) - summary.append([]) for column_cells in summary.columns: 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: 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 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: sheet.delete_cols(unmatched_col + 1, sheet.max_column - unmatched_col)