fix(ai-outfit): preview sample rows ignore Excel status (§19.9)
Add excel_service.read_all_rows (every valid data row, status-independent) and use it in _reload_sample_rows for the preview dropdown, so previewing still works after the whole sheet is 完成. Generation still uses load_outfit_tasks. +1 excel test (read_all_rows includes 完成/失败, skips incomplete); offscreen all-完成 sheet now yields sample rows; full suite (12) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -636,17 +636,20 @@ class AiOutfitPanel(QWidget):
|
|||||||
self._refresh_preview()
|
self._refresh_preview()
|
||||||
|
|
||||||
def _reload_sample_rows(self):
|
def _reload_sample_rows(self):
|
||||||
"""Best-effort: read the chosen Excel to fill the sample dropdown."""
|
"""Best-effort: read the chosen Excel to fill the sample dropdown.
|
||||||
|
|
||||||
|
Uses read_all_rows (status-independent) so preview still works after the
|
||||||
|
whole sheet is 完成 (docs/11 §10.3); generation still uses load_outfit_tasks.
|
||||||
|
"""
|
||||||
excel = self._excel_edit.text().strip()
|
excel = self._excel_edit.text().strip()
|
||||||
tasks = []
|
rows = []
|
||||||
if excel:
|
if excel:
|
||||||
try:
|
try:
|
||||||
from services.excel_service import load_outfit_tasks
|
from services.excel_service import read_all_rows
|
||||||
tasks = load_outfit_tasks(
|
rows = read_all_rows(excel)
|
||||||
excel, retry_failed=self._retry_failed_chk.isChecked())
|
|
||||||
except Exception as exc: # noqa: BLE001 - silent for preview
|
except Exception as exc: # noqa: BLE001 - silent for preview
|
||||||
logger.info("Sample rows unavailable: %s", exc)
|
logger.info("Sample rows unavailable: %s", exc)
|
||||||
self._fill_sample_combo(tasks)
|
self._fill_sample_combo(rows)
|
||||||
|
|
||||||
def _refresh_preview(self):
|
def _refresh_preview(self):
|
||||||
if not hasattr(self, "_preview_view"):
|
if not hasattr(self, "_preview_view"):
|
||||||
|
|||||||
@@ -113,6 +113,40 @@ def load_outfit_tasks(excel_path, retry_failed=False):
|
|||||||
workbook.close()
|
workbook.close()
|
||||||
|
|
||||||
|
|
||||||
|
def read_all_rows(excel_path):
|
||||||
|
"""Return an OutfitTask for every valid data row, ignoring E-column status.
|
||||||
|
|
||||||
|
Unlike load_outfit_tasks (which filters for *processable* rows), this returns
|
||||||
|
all rows whose title/product/garment are filled — including 「完成」/「失败」 —
|
||||||
|
for preview/sample purposes only (docs/11 §10.3). Rows missing any of those
|
||||||
|
fields are skipped.
|
||||||
|
"""
|
||||||
|
path = Path(excel_path)
|
||||||
|
workbook = load_workbook(str(path))
|
||||||
|
try:
|
||||||
|
sheet = workbook.worksheets[0]
|
||||||
|
rows = []
|
||||||
|
for row_index in range(2, sheet.max_row + 1):
|
||||||
|
title = sheet.cell(row_index, COL_TITLE).value
|
||||||
|
product_id = sheet.cell(row_index, COL_PRODUCT_ID).value
|
||||||
|
garment_path = sheet.cell(row_index, COL_GARMENT_PATH).value
|
||||||
|
if _is_empty(title) or _is_empty(product_id) or _is_empty(garment_path):
|
||||||
|
continue
|
||||||
|
raw_status = sheet.cell(row_index, COL_STATUS).value
|
||||||
|
rows.append(
|
||||||
|
OutfitTask(
|
||||||
|
row_index=row_index,
|
||||||
|
title=_cell_text(title),
|
||||||
|
product_id=_cell_text(product_id),
|
||||||
|
garment_path=_cell_text(garment_path),
|
||||||
|
status=_cell_text(raw_status) or STATUS_PENDING,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
finally:
|
||||||
|
workbook.close()
|
||||||
|
|
||||||
|
|
||||||
def write_outfit_result(excel_path, result):
|
def write_outfit_result(excel_path, result):
|
||||||
"""Write one outfit result to columns D/E/F and save immediately."""
|
"""Write one outfit result to columns D/E/F and save immediately."""
|
||||||
if not isinstance(result, OutfitResult):
|
if not isinstance(result, OutfitResult):
|
||||||
|
|||||||
@@ -1121,6 +1121,6 @@
|
|||||||
|
|
||||||
问题:预览样本行用 `load_outfit_tasks()`(给生成用、跳过「完成」行)填,整表生成成功后返回空 → 下拉只剩「(选 Excel 后显示替换效果)」。预览应不看状态。
|
问题:预览样本行用 `load_outfit_tasks()`(给生成用、跳过「完成」行)填,整表生成成功后返回空 → 下拉只剩「(选 Excel 后显示替换效果)」。预览应不看状态。
|
||||||
|
|
||||||
- [ ] `services/excel_service.py`:新增 `read_all_rows(excel)`,返回每一有效数据行(标题/货号/衣服图齐全)的 `OutfitTask`,**忽略 E 列状态**
|
- [x] `services/excel_service.py`:新增 `read_all_rows(excel)`,返回每一有效数据行(标题/货号/衣服图齐全)的 `OutfitTask`,**忽略 E 列状态**
|
||||||
- [ ] `ai_outfit_panel._reload_sample_rows()` 改调 `read_all_rows`(预览专用);生成仍走 `load_outfit_tasks`,`_on_tasks_loaded` 仍用实际任务
|
- [x] `ai_outfit_panel._reload_sample_rows()` 改调 `read_all_rows`(预览专用);生成仍走 `load_outfit_tasks`,`_on_tasks_loaded` 仍用实际任务
|
||||||
- [ ] 单测 `tests/test_excel_service.py`:`read_all_rows` 含「完成」行也返回、空字段行跳过
|
- [x] 单测 `tests/test_excel_service.py`:`read_all_rows` 含「完成」「失败」行也返回、空字段行跳过(8 用例);离屏验证全表完成的 Excel 预览下拉有样本行;全套 12 文件绿
|
||||||
|
|||||||
@@ -96,6 +96,18 @@ class TestOutfitExcelService(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(self._read_row(2), (None, "失败", "衣服图打不开"))
|
self.assertEqual(self._read_row(2), (None, "失败", "衣服图打不开"))
|
||||||
|
|
||||||
|
def test_read_all_rows_includes_done_failed_skips_incomplete(self):
|
||||||
|
from services.excel_service import read_all_rows
|
||||||
|
|
||||||
|
rows = read_all_rows(self.excel_path)
|
||||||
|
|
||||||
|
# rows 2(待处理) 3(完成) 4(失败) 6(待处理) — row 5 (空衣服图) skipped
|
||||||
|
self.assertEqual([r.row_index for r in rows], [2, 3, 4, 6])
|
||||||
|
by_idx = {r.row_index: r for r in rows}
|
||||||
|
self.assertEqual(by_idx[3].status, "完成")
|
||||||
|
self.assertEqual(by_idx[4].status, "失败")
|
||||||
|
self.assertEqual(by_idx[6].title, " 保留空格 ") # raw text preserved
|
||||||
|
|
||||||
def test_check_excel_writable_true_for_existing_workbook(self):
|
def test_check_excel_writable_true_for_existing_workbook(self):
|
||||||
from services.excel_service import check_excel_writable
|
from services.excel_service import check_excel_writable
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user