From 9d69bb3cd75e5840326ce0af4a815221fdfaea36 Mon Sep 17 00:00:00 2001 From: lhl Date: Sat, 8 Aug 2026 15:30:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=20?= =?UTF-8?q?data=5Fmodels=EF=BC=88design=20=C2=A73+=C2=A79.4=EF=BC=8Cfuture?= =?UTF-8?q?.annotations=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/genesis/data_models.py | 225 +++++++++++++++++++++++++++++++++++++ tests/test_data_models.py | 77 +++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 src/genesis/data_models.py create mode 100644 tests/test_data_models.py diff --git a/src/genesis/data_models.py b/src/genesis/data_models.py new file mode 100644 index 0000000..66108d7 --- /dev/null +++ b/src/genesis/data_models.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + + +class SheetType(Enum): + """Excel Sheet 的类型(Parser SheetDetector 判定结果)""" + FUNCTION = "FUNCTION" + SCREEN = "SCREEN" + REPORT = "REPORT" + DATABASE = "DATABASE" + INTERFACE = "INTERFACE" + BATCH = "BATCH" + MASTER = "MASTER" + GENERIC = "GENERIC" + + +class ElementType(Enum): + """Impact Agent 抽取的构成要素类型""" + FUNCTION = "機能" + SCREEN = "画面" + REPORT = "帳票" + DB = "DB" + IF = "IF" + BATCH = "バッチ" + + +class RelationType(Enum): + """关联类型(Impact Agent 推理结果)""" + USE = "利用" + REFER = "参照" + UPDATE = "更新" + OUTPUT = "输出" + INPUT = "输入" + DEPEND = "依赖" + + +class Confidence(Enum): + """置信度等级""" + HIGH = "high" + MEDIUM = "medium" + LOW = "low" + + +class ExtractionMethod(Enum): + """Excel 表的抽取方式""" + OPENPYXL = "openpyxl" + LLM_FROM_FREE_TEXT = "llm_from_free_text" + + +@dataclass +class Provenance: + file_name: str + sheet_name: str + row: int + column: str + column_header: str + + +@dataclass +class CellFormatting: + strikethrough: bool = False + font_color: str | None = None + bg_color: str | None = None + + +@dataclass +class CellComment: + author: str + text: str + source_uri: str + + +@dataclass +class CellValue: + value: Any + provenance: Provenance + formatting: CellFormatting | None = None + comment: CellComment | None = None + + +@dataclass +class ExcelTable: + name: str + detected_type: SheetType + extraction_method: str # 取 ExtractionMethod 的 value(同一常量来源) + headers: list[str] + rows: list[dict[str, "CellValue"]] + + +@dataclass +class ChapterMarker: + type: str # "heading" | "bookmark" | "placeholder" + name: str + level: int + + +@dataclass +class ParsedTemplate: + file_name: str + sections: list[ChapterMarker] + placeholders: dict[str, str] + styles: dict + + +@dataclass +class RuleDocument: + file_name: str + category: str # "write" | "design" | "ref" + markdown_content: str + source_path: str + file_type: str # "word" | "excel" | "ppt" + hash: str + + +@dataclass +class ImageAnalysis: + """图片分析结果(Parser 组装,StructuredSource 消费)""" + image_ref: str + description: str + confidence: float + source_uri: str + sheet_name: str + anchor_cell: str + status: str # "recognized" | "recorded_only" | "failed" + nearby_text: str = "" + + +@dataclass +class ControllerInfo: + name: str + class_name: str + path: str + base_path: str + endpoints: list[str] + source_uri: str + + +@dataclass +class ServiceInfo: + name: str + class_name: str + path: str + methods: list[str] + source_uri: str + + +@dataclass +class EntityInfo: + name: str + class_name: str + path: str + table_name: str | None + fields: list[str] + source_uri: str + + +@dataclass +class EndpointInfo: + method: str + path: str + controller: str | None + description: str + source_uri: str + + +@dataclass +class ExistingSystemInfo: + controller_layer: list[ControllerInfo] + service_layer: list[ServiceInfo] + entity_layer: list[EntityInfo] + api_endpoints: list[EndpointInfo] + source_path: str + + +@dataclass +class UnifiedDocument: + """FileReader 的统一输出(多格式归一化)""" + file_name: str + file_type: str # "excel" | "word" | "ppt" | "text" + source_path: str + content_type: str + tables: list[list[list[Any]]] | None = None + sheet_names: list[str] | None = None + paragraphs: list[dict] | None = None + slides: list[dict] | None = None + text: str | None = None + encoding: str | None = None + + +@dataclass +class CodeStructure: + """CodeParser 的解析输出""" + root_path: str + language: str + modules: list[dict] + classes: list[dict] + controllers: list[ControllerInfo] + services: list[ServiceInfo] + entities: list[EntityInfo] + endpoints: list[EndpointInfo] + raw_imports: list[dict] + + +@dataclass +class ImageDescription: + """ImageAnalyzer 的原始识别输出(工具层;业务侧用 ImageAnalysis)""" + image_ref: str + description: str + objects: list[str] + ocr_text: str | None + confidence: float + model: str + + +@dataclass +class StructuredSource: + tables: list[ExcelTable] + template: ParsedTemplate + rule_docs: list[RuleDocument] + image_analyses: list[ImageAnalysis] + existing_system: ExistingSystemInfo | None + comments: list[CellComment] \ No newline at end of file diff --git a/tests/test_data_models.py b/tests/test_data_models.py new file mode 100644 index 0000000..10e1d97 --- /dev/null +++ b/tests/test_data_models.py @@ -0,0 +1,77 @@ +from dataclasses import asdict + +from genesis.data_models import ( + CellComment, CellFormatting, CellValue, Confidence, ElementType, + ExcelTable, ExtractionMethod, ImageAnalysis, ParsedTemplate, Provenance, + RelationType, RuleDocument, SheetType, StructuredSource, +) + + +def test_sheettype_has_8_members(): + assert len(SheetType) == 8 + assert SheetType.FUNCTION.value == "FUNCTION" + assert SheetType.GENERIC.value == "GENERIC" + + +def test_value_enum_members(): + assert ElementType.FUNCTION.value == "機能" + assert RelationType.USE.value == "利用" + assert Confidence.HIGH.value == "high" + assert ExtractionMethod.OPENPYXL.value == "openpyxl" + + +def test_cellformatting_defaults(): + fmt = CellFormatting() + assert fmt.strikethrough is False + assert fmt.font_color is None + assert fmt.bg_color is None + + +def test_cellvalue_forward_reference_works(): + """CellValue 引用后置定义的 CellFormatting/CellComment(future.annotations 落地)""" + prov = Provenance(file_name="f.xlsx", sheet_name="S", row=1, column="A", column_header="h") + cv = CellValue( + value="x", + provenance=prov, + formatting=CellFormatting(strikethrough=True), + comment=CellComment(author="reviewer", text="check", source_uri="f.xlsx#S!A1"), + ) + assert cv.formatting.strikethrough is True + assert cv.comment.author == "reviewer" + + +def test_excel_table_references_sheettype(): + table = ExcelTable( + name="機能一覧", + detected_type=SheetType.FUNCTION, + extraction_method=ExtractionMethod.OPENPYXL.value, + headers=["機能ID", "機能名"], + rows=[], + ) + assert table.detected_type is SheetType.FUNCTION + assert table.extraction_method == "openpyxl" + + +def test_structured_source_assembles_all(): + source = StructuredSource( + tables=[], + template=ParsedTemplate(file_name="t.docx", sections=[], placeholders={}, styles={}), + rule_docs=[RuleDocument( + file_name="記入規則.docx", category="write", markdown_content="# 規則", + source_path="samples/記入規則.docx", file_type="word", hash="abc", + )], + image_analyses=[ImageAnalysis( + image_ref="img1", description="画面遷移図", confidence=0.9, + source_uri="f.xlsx#S!A1", sheet_name="S", anchor_cell="A1", status="recognized", + )], + existing_system=None, + comments=[], + ) + assert source.rule_docs[0].category == "write" + assert source.image_analyses[0].nearby_text == "" + + +def test_asdict_serializable(): + prov = Provenance(file_name="f.xlsx", sheet_name="S", row=1, column="A", column_header="h") + d = asdict(CellValue(value=1, provenance=prov)) + assert d["provenance"]["row"] == 1