feat(T-002): 建立核心数据模型与状态枚举
- src/core/status.py: TaskStatus 六态枚举,英文成员 + display_name/from_display 中英映射(单一事实源),对齐 04-architecture.md §3.4。 - src/core/models.py: OrderTask dataclass,字段集合/类型对齐 §3.3;支付模式不挂模型。 - tests/test_models.py: 枚举完整性/双向映射/未知值报错/默认值/实例独立性。 - 文档同步:04 §3.4 约定补 from_display;tasks/progress/current-state 更新。 验证:compileall OK;unittest Ran 10 tests, OK。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2863bf2750
commit
c41f37a27d
@@ -169,7 +169,7 @@ OrderTask
|
||||
|
||||
约定:
|
||||
|
||||
- 中↔英映射只在写入 Excel 或渲染 GUI 的边界处发生(建议由 `TaskStatus` 提供 `display_name` 属性集中维护),核心逻辑只比较英文枚举成员。
|
||||
- 中↔英映射只在写入 Excel 或渲染 GUI 的边界处发生,由 `TaskStatus` 的 `display_name` 属性与 `from_display` 反查集中维护(单一事实源),核心逻辑只比较英文枚举成员。
|
||||
- `OrderTask.status` 字段类型为 `TaskStatus`,不直接存中文字符串。
|
||||
- 注意区分「任务状态」与「执行器状态」:上表是单条任务的 `status`;批次级的「暂停」属于执行器状态,不进入本枚举(见 §4.5)。
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-06-24
|
||||
- 阶段:Phase 0 地基(T-001 完成)/ 原型验证前
|
||||
- 阶段:Phase 0 地基(T-001、T-002 完成)/ 原型验证前
|
||||
- 技术栈:Python 3.10(骨架已跑通);计划接入 PySide6 + `uiautomator2` + ADB + `openpyxl`
|
||||
- 生产代码:已初始化最小骨架(`src/main.py` 最小入口)
|
||||
- 测试:标准库 `unittest` 冒烟测试(`tests/test_smoke.py`)
|
||||
- 生产代码:骨架 + 核心数据模型(`src/main.py` 入口、`src/core/` 状态枚举与任务模型)
|
||||
- 测试:标准库 `unittest`,覆盖冒烟与核心模型(`tests/test_smoke.py`、`tests/test_models.py`)
|
||||
- 数据:尚未提供真实 Excel 样例
|
||||
|
||||
## 当前目录要点
|
||||
@@ -18,7 +18,7 @@
|
||||
| 路径 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `docs/` | 已有 | 项目规范化文档,已按 harness coding 文档风格建立。 |
|
||||
| `src/` | 已有 | 已建最小入口 `src/main.py`;GUI/执行器/Excel/Android 模块由后续任务添加。 |
|
||||
| `src/` | 已有 | `main.py` 入口 + `core/`(`status.py` 状态枚举、`models.py` 任务模型);GUI/执行器/Excel/Android 模块后续添加。 |
|
||||
| `tests/` | 已有 | 已建冒烟测试 `tests/test_smoke.py`(unittest)。 |
|
||||
| `requirements.txt` | 已有 | 依赖清单;当前零第三方运行时依赖,含依赖纪律注释。 |
|
||||
| `samples/` | 待建 | 脱敏 Excel 样例。 |
|
||||
@@ -29,9 +29,9 @@
|
||||
|
||||
任务定义以 [`../tasks.md`](../tasks.md) 为准,执行记录以 [`../progress.md`](../progress.md) 为准。
|
||||
|
||||
- 已完成:项目文档初始化;`T-001 初始化 Python 项目骨架`。
|
||||
- 已完成:项目文档初始化;`T-001 初始化 Python 项目骨架`;`T-002 建立核心数据模型和状态枚举`。
|
||||
- 正在进行:无。
|
||||
- 下一个可领取任务:`T-002 建立核心数据模型和状态枚举`。
|
||||
- 下一个可领取任务:`T-003 建立日志和运行产物目录策略`。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
|
||||
+11
-3
@@ -14,9 +14,9 @@
|
||||
## 当前摘要
|
||||
|
||||
- 日期:2026-06-24
|
||||
- 阶段:Phase 0 地基(T-001 完成)/ 原型验证前
|
||||
- 当前任务:无(T-001 已完成)
|
||||
- 下一个任务:`T-002 建立核心数据模型和状态枚举`
|
||||
- 阶段:Phase 0 地基(T-001、T-002 完成)/ 原型验证前
|
||||
- 当前任务:无(T-002 已完成)
|
||||
- 下一个任务:`T-003 建立日志和运行产物目录策略`
|
||||
- 当前阻塞:尚未提供真实 Excel 样例、Android 真机和测试商品链接
|
||||
|
||||
## 进度记录
|
||||
@@ -58,6 +58,14 @@
|
||||
- `python3 src/main.py` → 正常输出,exit=0
|
||||
- 范围:未提前创建 models/GUI/Android 等后续任务模块;`logs/`、`artifacts/`、`samples/` 归 T-003。
|
||||
|
||||
### 2026-06-24 · T-002 建立核心数据模型和状态枚举(DONE)
|
||||
|
||||
- 完成:`src/core/status.py` 定义 `TaskStatus`(六态,英文成员 + `display_name`/`from_display` 中英映射,单一事实源);`src/core/models.py` 定义 `OrderTask` dataclass,字段集合/类型对齐 `04-architecture.md` §3.3。
|
||||
- 测试:`tests/test_models.py` 覆盖枚举完整性、中英映射双向往返、未知值报错、`OrderTask` 默认值与 `sku_items` 实例独立性。
|
||||
- 文档:`04-architecture.md` §3.4 约定同步为 `display_name` + `from_display`。
|
||||
- 范围:未含 `PaymentConfig`/`PaymentMode`/`RunnerState`(属支付与执行器任务,按需引入)。
|
||||
- 验证:`python3 -m compileall src` OK;`python3 -m unittest discover -s tests -t .` → Ran 10 tests, OK。
|
||||
|
||||
## 阻塞与风险
|
||||
|
||||
- 真实 Excel 表头待提供。
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""核心模型、状态枚举与任务编排。"""
|
||||
@@ -0,0 +1,47 @@
|
||||
"""核心数据模型。
|
||||
|
||||
OrderTask 对齐 docs/04-architecture.md §3.3 的内部任务模型字段集合与类型。
|
||||
字段顺序因 dataclass「无默认值字段须在有默认值字段之前」的约束做了分组调整,
|
||||
字段集合、类型与语义与文档一致。支付模式不挂在本模型上(见 §3.3 说明与 §4.4 PaymentConfig)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from src.core.status import TaskStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrderTask:
|
||||
"""一条已审核的拼多多下单任务。
|
||||
|
||||
必填字段(来自 Excel 必填列)在前,其余可选字段与执行期回填字段带默认值。
|
||||
本模型只承载数据,不做业务校验;表头/数量等校验在 Excel 导入模块(T-201)。
|
||||
"""
|
||||
|
||||
# 必填:来自 Excel 的核心任务信息
|
||||
task_id: str
|
||||
row_index: int
|
||||
product_url: str
|
||||
sku_text: str
|
||||
quantity: int
|
||||
|
||||
# 可选:来自 Excel 的辅助信息
|
||||
sku_items: dict[str, str] = field(default_factory=dict)
|
||||
product_name: str | None = None
|
||||
receiver: str | None = None
|
||||
phone: str | None = None
|
||||
address: str | None = None
|
||||
note: str | None = None
|
||||
expected_amount: Decimal | None = None
|
||||
max_pay_amount: Decimal | None = None
|
||||
|
||||
# 执行期回填
|
||||
status: TaskStatus = TaskStatus.PENDING
|
||||
order_no: str | None = None
|
||||
fail_reason: str | None = None
|
||||
screenshot_path: str | None = None
|
||||
started_at: datetime | None = None
|
||||
finished_at: datetime | None = None
|
||||
@@ -0,0 +1,50 @@
|
||||
"""任务状态枚举(TaskStatus)。
|
||||
|
||||
与 docs/04-architecture.md §3.4 的状态机和中英映射表保持一致:
|
||||
代码使用英文枚举成员,中文仅用于 GUI 展示与 Excel 回写。
|
||||
中↔英映射集中在本模块维护(display_name / from_display),单一事实源,
|
||||
核心逻辑只比较英文枚举成员,不直接比较中文字符串。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TaskStatus(Enum):
|
||||
"""单条下单任务的状态。"""
|
||||
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
MANUAL = "manual"
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""返回写入 Excel / 展示在 GUI 的中文名。"""
|
||||
return _DISPLAY_NAMES[self]
|
||||
|
||||
@classmethod
|
||||
def from_display(cls, text: str) -> "TaskStatus":
|
||||
"""由中文显示名反查枚举,供读回结果文件等边界场景使用。
|
||||
|
||||
未知文本抛 ValueError,不静默兜底,避免吞掉脏数据。
|
||||
"""
|
||||
try:
|
||||
return _BY_DISPLAY[text]
|
||||
except KeyError:
|
||||
raise ValueError(f"未知的任务状态显示名:{text!r}") from None
|
||||
|
||||
|
||||
# 中英映射集中在此,作为单一事实源,避免散落各处导致漂移。
|
||||
_DISPLAY_NAMES: dict[TaskStatus, str] = {
|
||||
TaskStatus.PENDING: "待执行",
|
||||
TaskStatus.RUNNING: "执行中",
|
||||
TaskStatus.MANUAL: "待人工",
|
||||
TaskStatus.SUCCESS: "成功",
|
||||
TaskStatus.FAILED: "失败",
|
||||
TaskStatus.CANCELLED: "已取消",
|
||||
}
|
||||
|
||||
_BY_DISPLAY: dict[str, TaskStatus] = {zh: status for status, zh in _DISPLAY_NAMES.items()}
|
||||
@@ -21,7 +21,7 @@
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-001 | 初始化 Python 项目骨架 | - | 建立 `src/`、`tests/`、`requirements.txt`;可运行最小入口;不引入无关依赖;用**真实可运行命令**替换 `00-ai-start-here.md`、`03-tech-stack.md`、`05-coding-rules.md`、`current-state.md` 中的占位符验证命令 | DONE |
|
||||
| T-002 | 建立核心数据模型和状态枚举 | T-001 | `OrderTask`、`TaskStatus` 与 `04-architecture.md` 一致;有基础单元测试 | TODO |
|
||||
| T-002 | 建立核心数据模型和状态枚举 | T-001 | `OrderTask`、`TaskStatus` 与 `04-architecture.md` 一致;有基础单元测试 | DONE |
|
||||
| T-003 | 建立日志和运行产物目录策略 | T-001 | 日志、截图、UI XML 输出路径清晰;不记录敏感信息 | TODO |
|
||||
|
||||
## Phase 1 · 最高风险验证
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"""核心数据模型与状态枚举测试(T-002)。"""
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from src.core.models import OrderTask
|
||||
from src.core.status import TaskStatus
|
||||
|
||||
|
||||
class TaskStatusTest(unittest.TestCase):
|
||||
# 与 04-architecture.md §3.4 中英映射表保持一致
|
||||
EXPECTED = {
|
||||
TaskStatus.PENDING: "待执行",
|
||||
TaskStatus.RUNNING: "执行中",
|
||||
TaskStatus.MANUAL: "待人工",
|
||||
TaskStatus.SUCCESS: "成功",
|
||||
TaskStatus.FAILED: "失败",
|
||||
TaskStatus.CANCELLED: "已取消",
|
||||
}
|
||||
|
||||
def test_members_complete(self):
|
||||
"""枚举成员恰好为文档定义的六个。"""
|
||||
self.assertEqual(set(TaskStatus), set(self.EXPECTED))
|
||||
|
||||
def test_display_name(self):
|
||||
for status, zh in self.EXPECTED.items():
|
||||
self.assertEqual(status.display_name, zh)
|
||||
|
||||
def test_display_names_unique(self):
|
||||
names = [s.display_name for s in TaskStatus]
|
||||
self.assertEqual(len(names), len(set(names)))
|
||||
|
||||
def test_from_display_roundtrip(self):
|
||||
for status in TaskStatus:
|
||||
self.assertIs(TaskStatus.from_display(status.display_name), status)
|
||||
|
||||
def test_from_display_unknown_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
TaskStatus.from_display("不存在的状态")
|
||||
|
||||
|
||||
class OrderTaskTest(unittest.TestCase):
|
||||
def _minimal(self) -> OrderTask:
|
||||
return OrderTask(
|
||||
task_id="T1",
|
||||
row_index=2,
|
||||
product_url="https://example.com/p",
|
||||
sku_text="颜色=黑色;尺码=XL",
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
def test_defaults(self):
|
||||
task = self._minimal()
|
||||
self.assertEqual(task.status, TaskStatus.PENDING)
|
||||
self.assertEqual(task.sku_items, {})
|
||||
self.assertIsNone(task.order_no)
|
||||
self.assertIsNone(task.expected_amount)
|
||||
|
||||
def test_sku_items_not_shared(self):
|
||||
"""default_factory 确保每个实例拥有独立 dict,不互相串改。"""
|
||||
a = self._minimal()
|
||||
b = self._minimal()
|
||||
a.sku_items["颜色"] = "黑色"
|
||||
self.assertEqual(b.sku_items, {})
|
||||
|
||||
def test_full_construction(self):
|
||||
task = OrderTask(
|
||||
task_id="T2",
|
||||
row_index=3,
|
||||
product_url="https://example.com/p2",
|
||||
sku_text="套餐=单件",
|
||||
quantity=2,
|
||||
sku_items={"套餐": "单件"},
|
||||
product_name="测试商品",
|
||||
expected_amount=Decimal("9.90"),
|
||||
max_pay_amount=Decimal("10.00"),
|
||||
status=TaskStatus.SUCCESS,
|
||||
order_no="2026000001",
|
||||
started_at=datetime(2026, 6, 24, 10, 0, 0),
|
||||
)
|
||||
self.assertEqual(task.quantity, 2)
|
||||
self.assertEqual(task.expected_amount, Decimal("9.90"))
|
||||
self.assertEqual(task.status, TaskStatus.SUCCESS)
|
||||
self.assertEqual(task.sku_items["套餐"], "单件")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user