From 539945be677a1958e2b7cafd878886cd81f0285f Mon Sep 17 00:00:00 2001 From: lhl Date: Sun, 9 Aug 2026 04:04:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20data=5Fmodels=20=E6=89=A9=E5=B1=95=20Mi?= =?UTF-8?q?xedParagraph/MixedSheet=EF=BC=88=E6=AE=B5=E8=90=BD=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _AI_USAGE_LOG.md | 1 + src/genesis/data_models.py | 23 ++++++++++++++++++++--- src/genesis/parsers/excel_parser.py | 3 ++- tests/test_excel_parser.py | 24 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/_AI_USAGE_LOG.md b/_AI_USAGE_LOG.md index 7700856..39230ad 100644 --- a/_AI_USAGE_LOG.md +++ b/_AI_USAGE_LOG.md @@ -33,3 +33,4 @@ | 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 | | 2026-08-09 03:59 | Agent 实现 | 里程碑2.5 Task9 实现:段落分割器 split_paragraphs(MIXED 完整段落解析第一步,纯函数)。新建 src/genesis/parsers/paragraph_splitter.py(_is_blank_row 行空白判定 + split_paragraphs(matrix)→list[(start,end)] 含端点 0-based,以全空行为界返回段落区间,空矩阵→[])与 tests/test_paragraph_splitter.py(5 用例:空矩阵/单段落无空行/中间空行分割/尾部空行不产生多余段落/前导空行从首个非空行开始);TDD 验证 RED(ModuleNotFoundError: No module named 'genesis.parsers.paragraph_splitter')→ GREEN(5 passed);pytest 全量 46 passed(41 baseline + 5 新增);不依赖 openpyxl 与 data_models;提交 34ef28b | src/genesis/parsers/paragraph_splitter.py, tests/test_paragraph_splitter.py | deepseek-v4-flash-free | +| 2026-08-09 04:20 | Agent 实现 | 里程碑2.5 Task10 实现:data_models 扩展 MixedParagraph/MixedSheet 段落容器,并新增 ExcelParseResult.mixed 字段(默认空列表)。TDD:先追加 3 个测试(字段存在性与默认行为)确认 RED(ImportError: cannot import name 'MixedParagraph'),再在 data_models.py 末尾追加两个 dataclass(顶部 from typing 加 Literal、dataclasses 加 field),excel_parser.py 更新导入并为 ExcelParseResult 加 mixed 字段,确认 GREEN(3 passed),全量 pytest 49 passed(46 baseline + 3)。未修改既有类与字段,仅追加。 | src/genesis/data_models.py, src/genesis/parsers/excel_parser.py, tests/test_excel_parser.py | deepseek-v4-flash-free | diff --git a/src/genesis/data_models.py b/src/genesis/data_models.py index 66108d7..7b5d024 100644 --- a/src/genesis/data_models.py +++ b/src/genesis/data_models.py @@ -1,8 +1,8 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum -from typing import Any +from typing import Any, Literal class SheetType(Enum): @@ -222,4 +222,21 @@ class StructuredSource: rule_docs: list[RuleDocument] image_analyses: list[ImageAnalysis] existing_system: ExistingSystemInfo | None - comments: list[CellComment] \ No newline at end of file + comments: list[CellComment] + + +@dataclass +class MixedParagraph: + """混合 sheet 的一个段落(表格或自由文本)""" + kind: Literal["table", "free_text"] + matrix: list[list[Any]] | None = None # 该段原始矩阵(调试/重现) + table: ExcelTable | None = None # kind="table" 时填充 + text: str | None = None # kind="free_text" 时填充(段全文) + source_range: tuple[int, int] | None = None # (first_row, last_row) 矩阵 0-based + + +@dataclass +class MixedSheet: + """混合 sheet 的段落集合""" + name: str + paragraphs: list[MixedParagraph] = field(default_factory=list) \ No newline at end of file diff --git a/src/genesis/parsers/excel_parser.py b/src/genesis/parsers/excel_parser.py index db6bccb..daeb7c0 100644 --- a/src/genesis/parsers/excel_parser.py +++ b/src/genesis/parsers/excel_parser.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path -from genesis.data_models import CellComment, ExcelTable +from genesis.data_models import CellComment, ExcelTable, MixedSheet 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, find_header_row @@ -19,6 +19,7 @@ class ExcelParseResult: tables: list[ExcelTable] = field(default_factory=list) comments: list[CellComment] = field(default_factory=list) skipped: list[str] = field(default_factory=list) + mixed: list[MixedSheet] = field(default_factory=list) class ExcelParser: diff --git a/tests/test_excel_parser.py b/tests/test_excel_parser.py index b8e253e..1e6a6b1 100644 --- a/tests/test_excel_parser.py +++ b/tests/test_excel_parser.py @@ -57,3 +57,27 @@ def test_parse_attaches_strikethrough_formatting(tmp_path): cv = result.tables[0].rows[0]["機能ID"] assert cv.formatting is not None assert cv.formatting.strikethrough is True + + +from genesis.data_models import MixedParagraph, MixedSheet +from genesis.parsers.excel_parser import ExcelParseResult + + +def test_excel_parse_result_has_mixed_default(): + r = ExcelParseResult(file_name="f.xlsx") + assert r.mixed == [] + + +def test_mixed_paragraph_defaults(): + p = MixedParagraph(kind="table") + assert p.table is None + assert p.text is None + assert p.source_range is None + + +def test_mixed_sheet_holds_paragraphs(): + p1 = MixedParagraph(kind="table") + p2 = MixedParagraph(kind="free_text", text="备注") + ms = MixedSheet(name="混合", paragraphs=[p1, p2]) + assert ms.name == "混合" + assert [p.kind for p in ms.paragraphs] == ["table", "free_text"]