fix: 里程碑2 最终评审 must-fix(header_row 接线 + formatting 回填)

This commit is contained in:
lhl
2026-08-09 03:25:32 +08:00
parent 3307d7ba72
commit eebed38886
4 changed files with 59 additions and 5 deletions
+1
View File
@@ -31,3 +31,4 @@
| 2026-08-09 03:13 | Agent 实现 | 里程碑2 Task7 实现:ExcelParser 编排器(集成全部解析模块)。创建 src/genesis/parsers/excel_parser.pyExcelParseResult dataclassfile_name/tables/comments/skippedExcelParser.parse 编排:open_workbook→逐 sheet sheet_matrix→空则跳过→detect_sheet_type→classify_sheet→FREE_TEXT 走 extract_text_blocks+build_free_text_table(带 detected_type)→否则 merged ranges 转 tuple 后 forward_fill+extract_table→collect_comments 汇总)与 tests/test_excel_parser.py2 个用例:表型 sheet 两个(機能一覧 FUNCTION / バッチ一覧 BATCH 类型+行数断言)、自由记述 sheet extraction_method="llm_from_free_text");TDD 验证 REDImportError: No module named 'genesis.parsers.excel_parser')→ GREEN2 passed);pytest 全量 36 passeddata_models.py 未改动;对照 dispatch 上下文说明:data_models 无 ParsedWorkbook 命名,按 brief 定义 ExcelParseResult | src/genesis/parsers/excel_parser.py, tests/test_excel_parser.py | deepseek-v4-flash-free |
| 2026-08-09 03:20 | 测试验证 | 里程碑2 Task8 实现:真实样本集成测试。创建 tests/test_real_samples.py3 个端到端用例,各样本缺失时 pytest.skip:新規開発→機能一覧 存在且 int rows+DB定義;追加改修→tables 非空且至少一表有数据行;自由記述→存在 extraction_method="llm_from_free_text" 的表);全量验证 39 passed(含 3 个样本用例);真实样本产物: 新規開発 6 表全 openpyxl(機能一覧9/画面一覧7/帳票一覧5/DB定義20/IF定義3/バッチ一覧3); 追加改修 2 表 openpyxl(機能10/画面7+ 改修ポイント 以 FREE_TEXT 分类改 1 行(MIXED 折叠落观察已确认); 自由記述 3 表全 llm_from_free_text 各 1 行; 3 样本均 0 批注、无空 sheet。调研结论:MIXED 已知缺口#1 在真实样本未触发断言失败(无断言触发),按 brief 保持弱断言 | tests/test_real_samples.py | deepseek-v4-flash-free |
| 2026-08-09 03:30 | 整体迭代 | 里程碑2 最终评审修正(must-fix x2):① find_header_row 结果未传入 extract_table(恒 header_row=0,标题行置上的 sheet 会损坏表头)→ excel_parser.py 结构化路径改用 find_header_row(filled) 并 <0 回落 0;② cell_formatting 死代码(CellValue.formatting 恒 None,取消线/背景色到不了下游)→ extract_table 新增可选 formatting_map 参数,excel_parser 遍历 ws.iter_rows 构建 fmt_map 传入。tests/test_excel_parser.py 新增 2 用例(标题行表头定位、strikethrough 回填);pytest 全量 41 passed | src/genesis/parsers/excel_parser.py, src/genesis/parsers/table_extractor.py, tests/test_excel_parser.py | deepseek-v4-flash-free |
+17 -3
View File
@@ -6,10 +6,10 @@ from pathlib import Path
from genesis.data_models import CellComment, ExcelTable
from genesis.parsers.excel_reader import open_workbook, sheet_matrix
from genesis.parsers.sheet_detector import detect_sheet_type
from genesis.parsers.sheet_nature import SheetNature, classify_sheet
from genesis.parsers.sheet_nature import SheetNature, classify_sheet, find_header_row
from genesis.parsers.merge_fill import forward_fill
from genesis.parsers.table_extractor import extract_table
from genesis.parsers.formatting_detector import collect_comments
from genesis.parsers.formatting_detector import cell_formatting, collect_comments
from genesis.parsers.free_text_extractor import build_free_text_table, extract_text_blocks
@@ -46,6 +46,20 @@ class ExcelParser:
for r in ws.merged_cells.ranges
]
filled = forward_fill(matrix, merged) if merged else matrix
result.tables.append(extract_table(ws.title, filled, file_name, detected_type))
header_row = find_header_row(filled)
if header_row < 0:
header_row = 0
fmt_map = {}
for row in ws.iter_rows():
for cell in row:
fmt = cell_formatting(cell)
if fmt is not None:
fmt_map[(cell.row - 1, cell.column - 1)] = fmt
result.tables.append(
extract_table(
ws.title, filled, file_name, detected_type,
header_row=header_row, formatting_map=fmt_map,
)
)
result.comments.extend(collect_comments(ws, file_name))
return result
+8 -2
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Any
from genesis.data_models import CellValue, ExcelTable, Provenance, SheetType
from genesis.data_models import CellFormatting, CellValue, ExcelTable, Provenance, SheetType
def column_letter(index: int) -> str:
@@ -20,13 +20,18 @@ def extract_table(
file_name: str,
detected_type: SheetType,
header_row: int = 0,
formatting_map: dict[tuple[int, int], CellFormatting] | None = None,
) -> ExcelTable:
"""从矩阵提取表格:首行视为表头,其后为数据行。"""
"""从矩阵提取表格:首行视为表头,其后为数据行。
formatting_map:矩阵坐标 (row, col)0 起)→ CellFormatting,用于还原取消线/背景色等样式。
"""
if not matrix:
return ExcelTable(
name=sheet_name, detected_type=detected_type,
extraction_method="openpyxl", headers=[], rows=[],
)
fmt_map = formatting_map or {}
headers = [str(c) if c is not None else "" for c in matrix[header_row]]
rows = []
for r in range(header_row + 1, len(matrix)):
@@ -42,6 +47,7 @@ def extract_table(
column=column_letter(c + 1),
column_header=h,
),
formatting=fmt_map.get((r, c)),
)
rows.append(row_dict)
return ExcelTable(
+33
View File
@@ -24,3 +24,36 @@ def test_parse_free_text_sheet(tmp_path):
result = ExcelParser().parse(path)
assert len(result.tables) == 1
assert result.tables[0].extraction_method == "llm_from_free_text"
def test_parse_uses_detected_header_row(tmp_path):
# 标题行(单格)在首行,真实表头在第二行
wb = new_workbook({
"機能一覧": [["機能一覧"], ["機能ID", "機能名"], ["A001", "社員登録"]],
})
path = save_workbook(tmp_path, wb)
result = ExcelParser().parse(path)
t = result.tables[0]
assert t.headers == ["機能ID", "機能名"]
assert len(t.rows) == 1
assert t.rows[0]["機能ID"].value == "A001"
def test_parse_attaches_strikethrough_formatting(tmp_path):
from copy import copy
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "機能一覧"
ws["A1"] = "機能ID"
ws["B1"] = "機能名"
ws["A2"] = "A001"
ws["B2"] = "社員登録"
font = copy(ws["A2"].font)
font.strike = True
ws["A2"].font = font
path = save_workbook(tmp_path, wb)
result = ExcelParser().parse(path)
cv = result.tables[0].rows[0]["機能ID"]
assert cv.formatting is not None
assert cv.formatting.strikethrough is True