From 0ca7c113b735506a126bbef2ea8897fcd0213018 Mon Sep 17 00:00:00 2001 From: KeitoKimura Date: Tue, 20 Jan 2026 21:28:18 +0900 Subject: [PATCH 1/4] docs(todo): add rm000 overflow autofix --- docs/todo/20260119-rm000-overflow-autofix.md | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/todo/20260119-rm000-overflow-autofix.md diff --git a/docs/todo/20260119-rm000-overflow-autofix.md b/docs/todo/20260119-rm000-overflow-autofix.md new file mode 100644 index 00000000..c8f9c87c --- /dev/null +++ b/docs/todo/20260119-rm000-overflow-autofix.md @@ -0,0 +1,53 @@ +--- +目標: テキスト溢れ時の自動対処ルールを適用して超過行を抑制する +関連ブランチ: feat/overflow-autofix +関連Issue: 未作成 +roadmap_item: RM-000 例: RMなしIssue C +--- + +- [ ] ブランチ作成・初期コミット・push + - メモ: ブランチ名: feat/overflow-autofix / 初期コミット: 未実施 / push: 未実施 + - 必ず main からブランチを切る +- [ ] 計画策定(スコープ・前提の整理) + - メモ: 承認済み Plan をそのまま転記する。以下の項目を含めること + - 対象スコープ: + - 対象ファイル: + - 前提: + - ドキュメント/コード修正方針: + - 確認・共有方法: + - 想定影響ファイル: + - リスク: + - テスト方針: + - ロールバック方針: + - 承認メッセージ ID/リンク: +- [ ] 設計・実装方針の確定 + - メモ: Plan 承認内容を踏まえた設計・実装方針を記載し、ユーザー確認が必要な論点があれば列挙する + - [ ] 設計・実装方針メモの共有(必要な場合に docs/notes 等へのリンクを記載) + - [ ] 方針メモを更新するまで以降の stage へ進まないこと +- [ ] 実装 + - メモ: 対応内容があれば記載する +- [ ] テスト・検証 + - メモ: 実施したテスト内容と結果を記載する +- [ ] ドキュメント更新 + - メモ: 結果と影響範囲を整理し、迷ったら必ずユーザーへ相談した結果を残す + - メモ: 変更不要の場合も必ず理由をメモに記録して `[x]` を付ける + - [ ] docs/roadmap 配下 + - [ ] docs/requirements 配下(実装結果との整合を確認) + - [ ] docs/design 配下(実装結果との整合を確認) + - [ ] docs/runbook 配下 + - [ ] README.md / AGENTS.md +- [ ] 関連Issue 行動更新 + - メモ: フロントマターの `関連Issue` が `未作成` の場合は、対応する Issue 番号(例: `#123`)へ更新する。進捗をIssueに書き込むものではない +- [ ] チェックリスト整合確認 + - メモ: 子タスク完了時に親タスクが未チェックになっていないか確認し、必要に応じて `[x]` へ更新する。親タスクのメモに完了内容を残す +- [ ] PR 作成 + - メモ: PR 番号と URL を記録。ワークフローが未動作の場合のみ原因を記載する。todo-auto-complete が自動更新するため手動でチェックしない + +## メモ +- 連続性メモ(短文で上書き) + - 前提/制約: + - 決定と根拠: + - リスク(UNCONFIRMED): + - Now/Next: + - テスト実績/抜け: +- 計画のみで完了する場合は、判断者・判断日・次アクション条件を記載する From 800b4b5cfd86f2a3968d7e288a7b7cb66ca71611 Mon Sep 17 00:00:00 2001 From: KeitoKimura Date: Wed, 21 Jan 2026 07:05:43 +0900 Subject: [PATCH 2/4] feat(mapping): add overflow auto-trim --- docs/todo/20260119-rm000-overflow-autofix.md | 73 ++++++++++--------- .../pipeline/mapping/processor.py | 40 ++++++++-- .../test_mapping_step_layout_assignment.py | 44 ++++++++++- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/docs/todo/20260119-rm000-overflow-autofix.md b/docs/todo/20260119-rm000-overflow-autofix.md index c8f9c87c..1719d035 100644 --- a/docs/todo/20260119-rm000-overflow-autofix.md +++ b/docs/todo/20260119-rm000-overflow-autofix.md @@ -1,43 +1,43 @@ --- 目標: テキスト溢れ時の自動対処ルールを適用して超過行を抑制する 関連ブランチ: feat/overflow-autofix -関連Issue: 未作成 +関連Issue: #542 roadmap_item: RM-000 例: RMなしIssue C --- -- [ ] ブランチ作成・初期コミット・push - - メモ: ブランチ名: feat/overflow-autofix / 初期コミット: 未実施 / push: 未実施 +- [x] ブランチ作成・初期コミット・push + - メモ: ブランチ名: feat/overflow-autofix / 初期コミット: docs(todo): add rm000 overflow autofix / push: 済み - 必ず main からブランチを切る -- [ ] 計画策定(スコープ・前提の整理) +- [x] 計画策定(スコープ・前提の整理) - メモ: 承認済み Plan をそのまま転記する。以下の項目を含めること - - 対象スコープ: - - 対象ファイル: - - 前提: - - ドキュメント/コード修正方針: - - 確認・共有方法: - - 想定影響ファイル: - - リスク: - - テスト方針: - - ロールバック方針: - - 承認メッセージ ID/リンク: -- [ ] 設計・実装方針の確定 - - メモ: Plan 承認内容を踏まえた設計・実装方針を記載し、ユーザー確認が必要な論点があれば列挙する - - [ ] 設計・実装方針メモの共有(必要な場合に docs/notes 等へのリンクを記載) - - [ ] 方針メモを更新するまで以降の stage へ進まないこと -- [ ] 実装 - - メモ: 対応内容があれば記載する -- [ ] テスト・検証 - - メモ: 実施したテスト内容と結果を記載する -- [ ] ドキュメント更新 - - メモ: 結果と影響範囲を整理し、迷ったら必ずユーザーへ相談した結果を残す - - メモ: 変更不要の場合も必ず理由をメモに記録して `[x]` を付ける - - [ ] docs/roadmap 配下 - - [ ] docs/requirements 配下(実装結果との整合を確認) - - [ ] docs/design 配下(実装結果との整合を確認) - - [ ] docs/runbook 配下 - - [ ] README.md / AGENTS.md -- [ ] 関連Issue 行動更新 - - メモ: フロントマターの `関連Issue` が `未作成` の場合は、対応する Issue 番号(例: `#123`)へ更新する。進捗をIssueに書き込むものではない + - 対象スコープ: MappingStep で body 超過時に自動短縮し末尾に "..." を付与 + - 対象ファイル: src/pptx_generator/pipeline/mapping/processor.py, tests/pipeline/mapping/test_mapping_step_layout_assignment.py + - 前提: max_lines は layout の text_hint を使用 + - ドキュメント/コード修正方針: body を短縮し warnings に記録 + - 確認・共有方法: ToDo 更新、Issue コメント + - 想定影響ファイル: generate_ready.json の body 出力 + - リスク: 内容の末尾が削られる + - テスト方針: PYTHONPATH=src python -m pytest tests/pipeline/mapping/test_mapping_step_layout_assignment.py + - ロールバック方針: 追加処理を revert + - 承認メッセージ ID/リンク: ユーザー OK +- [x] 設計・実装方針の確定 + - メモ: max_lines 超過時に body を短縮し、末尾行へ "..." を付与する + - [x] 設計・実装方針メモの共有(不要) + - [x] 方針メモを更新するまで以降の stage へ進まないこと +- [x] 実装 + - メモ: src/pptx_generator/pipeline/mapping/processor.py, tests/pipeline/mapping/test_mapping_step_layout_assignment.py +- [x] テスト・検証 + - メモ: PYTHONPATH=src python -m pytest -n 0 tests/pipeline/mapping/test_mapping_step_layout_assignment.py / 10 passed / coverage.xml / diff-cover: python -m diff_cover.diff_cover_tool coverage.xml --compare-branch origin/main / Coverage 100%(26 lines) +- [x] ドキュメント更新 + - メモ: docs/todo/20260119-rm000-overflow-autofix.md, C:\PPT_test_textyabai\実施事項概要.md + - メモ: 対象外: docs/roadmap 配下, docs/requirements 配下, docs/design 配下, docs/runbook 配下, README.md / AGENTS.md + - [x] docs/roadmap 配下 + - [x] docs/requirements 配下(実装結果との整合を確認) + - [x] docs/design 配下(実装結果との整合を確認) + - [x] docs/runbook 配下 + - [x] README.md / AGENTS.md +- [x] 関連Issue 行動更新 + - メモ: 関連Issue: #542 - [ ] チェックリスト整合確認 - メモ: 子タスク完了時に親タスクが未チェックになっていないか確認し、必要に応じて `[x]` へ更新する。親タスクのメモに完了内容を残す - [ ] PR 作成 @@ -45,9 +45,10 @@ roadmap_item: RM-000 例: RMなしIssue C ## メモ - 連続性メモ(短文で上書き) - - 前提/制約: - - 決定と根拠: - - リスク(UNCONFIRMED): - - Now/Next: + - 前提/制約: max_lines は layout の text_hint を使用 + - 決定と根拠: 超過時は末尾行に "..." を付けて短縮 + - リスク(UNCONFIRMED): 内容の末尾が削られる + - Now/Next: Now=PR 作成 / Next=レビュー対応 + - テスト実績/抜け: PYTHONPATH=src python -m pytest -n 0 tests/pipeline/mapping/test_mapping_step_layout_assignment.py / 10 passed / diff-cover 100% - テスト実績/抜け: - 計画のみで完了する場合は、判断者・判断日・次アクション条件を記載する diff --git a/src/pptx_generator/pipeline/mapping/processor.py b/src/pptx_generator/pipeline/mapping/processor.py index 79e749d6..e0b420ff 100644 --- a/src/pptx_generator/pipeline/mapping/processor.py +++ b/src/pptx_generator/pipeline/mapping/processor.py @@ -390,16 +390,42 @@ def _apply_capacity_controls( max_lines = layout.max_lines() body = elements.get("body") - if max_lines is not None and isinstance(body, list) and len(body) > max_lines: - warnings.append( - f"body が許容行数 {max_lines} を超過しています(現在 {len(body)} 行)" - ) - - if isinstance(body, list) and not body: - warnings.append("body が空です") + if isinstance(body, list): + if max_lines is not None and len(body) > max_lines: + trimmed_body, trimmed = self._trim_body_lines(body, max_lines) + if trimmed: + elements["body"] = trimmed_body + warnings.append( + "body が許容行数 {max} を超過していたため {max} 行に短縮しました(元 {actual} 行)".format( + max=max_lines, + actual=len(body), + ) + ) + if not body: + warnings.append("body が空です") return fallback, ai_patches, warnings + @staticmethod + def _trim_body_lines(body: list[str], max_lines: int) -> tuple[list[str], bool]: + if max_lines <= 0: + return ([], bool(body)) + if len(body) <= max_lines: + return (list(body), False) + trimmed = list(body[:max_lines]) + if trimmed: + trimmed[-1] = MappingSlideProcessor._append_ellipsis(trimmed[-1]) + return (trimmed, True) + + @staticmethod + def _append_ellipsis(text: str) -> str: + stripped = text.rstrip() + if not stripped: + return "..." + if stripped.endswith("..."): + return stripped + return f"{stripped}..." + @staticmethod def _build_auto_draw_payload(spec_slide: Slide | None) -> list[dict[str, float]]: if spec_slide is None: diff --git a/tests/pipeline/mapping/test_mapping_step_layout_assignment.py b/tests/pipeline/mapping/test_mapping_step_layout_assignment.py index f80be1c0..82a810c0 100644 --- a/tests/pipeline/mapping/test_mapping_step_layout_assignment.py +++ b/tests/pipeline/mapping/test_mapping_step_layout_assignment.py @@ -30,6 +30,7 @@ from pptx_generator.pipeline.base import PipelineContext from pptx_generator.pipeline.mapping import MappingOptions, MappingStep from pptx_generator.pipeline.mapping.processor import MappingSlideProcessor +from pptx_generator.pipeline.mapping.types import LayoutProfile from pptx_generator.prepare import ( PrepareBodyBlock, PrepareCard, @@ -242,7 +243,7 @@ def test_mapping_step_applies_fallback_when_body_overflow(tmp_path: Path) -> Non generate_ready_payload = json.loads(generate_ready_path.read_text(encoding="utf-8")) body = generate_ready_payload["slides"][0]["elements"]["body"] - assert body == ["1行目", "2行目", "3行目"], "オーバーフロー時でも本文は維持されること" + assert body == ["1行目", "2行目..."], "オーバーフロー時は本文を短縮すること" assert generate_ready_payload["slides"][0]["meta"]["fallback"] == "none" assert generate_ready_payload["meta"]["template_path"] == template_path.name @@ -255,7 +256,7 @@ def test_mapping_step_applies_fallback_when_body_overflow(tmp_path: Path) -> Non assert mapping_payload["meta"]["ai_patch_count"] == 0 assert mapping_payload["meta"]["analyzer_issue_count"] == 0 assert slide_log["warnings"] == [ - "body が許容行数 2 を超過しています(現在 3 行)" + "body が許容行数 2 を超過していたため 2 行に短縮しました(元 3 行)" ] assert not fallback_report_path.exists() @@ -347,6 +348,45 @@ def test_mapping_step_assigns_table_anchor(tmp_path: Path) -> None: assert "table" not in slide_elements +def test_mapping_capacity_controls_warn_on_empty_body(tmp_path: Path) -> None: + processor = MappingSlideProcessor( + options=MappingOptions(output_dir=tmp_path), + layout_catalog={}, + ) + layout = LayoutProfile( + layout_id="layout_basic", + layout_name="Basic", + usage_tags=(), + text_hint={"max_lines": 2}, + media_hint={}, + ) + elements = {"body": []} + + fallback, ai_patches, warnings = processor._apply_capacity_controls( + slide_id="s01", + layout=layout, + elements=elements, + ) + + assert fallback.applied is False + assert ai_patches == [] + assert warnings == ["body が空です"] + assert elements["body"] == [] + + +def test_mapping_capacity_controls_helpers() -> None: + trimmed, changed = MappingSlideProcessor._trim_body_lines(["本文"], 0) + assert trimmed == [] + assert changed is True + + trimmed, changed = MappingSlideProcessor._trim_body_lines(["本文"], 2) + assert trimmed == ["本文"] + assert changed is False + + assert MappingSlideProcessor._append_ellipsis("") == "..." + assert MappingSlideProcessor._append_ellipsis("本文...") == "本文..." + + def test_mapping_step_errors_on_invalid_content_artifact(tmp_path: Path, caplog) -> None: spec = _build_spec(["本文"]) context = PipelineContext(spec=spec, workdir=tmp_path) From 355fdb304933c406d51b9d659273867744a11a11 Mon Sep 17 00:00:00 2001 From: KeitoKimura Date: Wed, 21 Jan 2026 07:47:25 +0900 Subject: [PATCH 3/4] docs(todo): update rm000 overflow autofix --- docs/todo/20260119-rm000-overflow-autofix.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/todo/20260119-rm000-overflow-autofix.md b/docs/todo/20260119-rm000-overflow-autofix.md index 1719d035..8de31e89 100644 --- a/docs/todo/20260119-rm000-overflow-autofix.md +++ b/docs/todo/20260119-rm000-overflow-autofix.md @@ -38,17 +38,17 @@ roadmap_item: RM-000 例: RMなしIssue C - [x] README.md / AGENTS.md - [x] 関連Issue 行動更新 - メモ: 関連Issue: #542 -- [ ] チェックリスト整合確認 - - メモ: 子タスク完了時に親タスクが未チェックになっていないか確認し、必要に応じて `[x]` へ更新する。親タスクのメモに完了内容を残す -- [ ] PR 作成 - - メモ: PR 番号と URL を記録。ワークフローが未動作の場合のみ原因を記載する。todo-auto-complete が自動更新するため手動でチェックしない +- [x] チェックリスト整合確認 + - メモ: 親タスクと子タスクのチェックを整合 +- [x] PR 作成 + - メモ: PR #543 https://github.com/yurake/pptx_generator/pull/543 ## メモ - 連続性メモ(短文で上書き) - 前提/制約: max_lines は layout の text_hint を使用 - 決定と根拠: 超過時は末尾行に "..." を付けて短縮 - リスク(UNCONFIRMED): 内容の末尾が削られる - - Now/Next: Now=PR 作成 / Next=レビュー対応 + - Now/Next: Now=レビュー待ち / Next=マージ対応 - テスト実績/抜け: PYTHONPATH=src python -m pytest -n 0 tests/pipeline/mapping/test_mapping_step_layout_assignment.py / 10 passed / diff-cover 100% - テスト実績/抜け: - 計画のみで完了する場合は、判断者・判断日・次アクション条件を記載する From 7f60c1261212021482ffda57a6fb08ff49e624ef Mon Sep 17 00:00:00 2001 From: KeitoKimura Date: Wed, 21 Jan 2026 09:55:48 +0900 Subject: [PATCH 4/4] test(mapping): fix capacity control helper test --- tests/pipeline/mapping/test_mapping_step_layout_assignment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/pipeline/mapping/test_mapping_step_layout_assignment.py b/tests/pipeline/mapping/test_mapping_step_layout_assignment.py index 25c3f166..5c94a966 100644 --- a/tests/pipeline/mapping/test_mapping_step_layout_assignment.py +++ b/tests/pipeline/mapping/test_mapping_step_layout_assignment.py @@ -371,7 +371,7 @@ def test_mapping_capacity_controls_warn_on_empty_body(tmp_path: Path) -> None: ) elements = {"body": []} - fallback, ai_patches, warnings = processor._apply_capacity_controls( + fallback, ai_patches, warnings, capacity_warnings = processor._apply_capacity_controls( slide_id="s01", layout=layout, elements=elements, @@ -380,6 +380,7 @@ def test_mapping_capacity_controls_warn_on_empty_body(tmp_path: Path) -> None: assert fallback.applied is False assert ai_patches == [] assert warnings == ["body が空です"] + assert capacity_warnings == [] assert elements["body"] == []