diff --git a/paperforge/worker/base_views.py b/paperforge/worker/base_views.py index 04c664e6..56f43f05 100644 --- a/paperforge/worker/base_views.py +++ b/paperforge/worker/base_views.py @@ -39,7 +39,7 @@ PAPERFORGE_VIEW_PREFIX = "# PAPERFORGE_VIEW: " def build_base_views(domain: str) -> list[dict]: - """Build the 8-view list for a domain Base file. + """Build the 4-view list for a domain Base file. Uses workflow gate columns (has_pdf, do_ocr, analyze, ocr_status) matching the master version's Base views. See REQUIREMENTS.md @@ -49,7 +49,7 @@ def build_base_views(domain: str) -> list[dict]: domain: The domain name (e.g., "骨科"). Returns: - List of 8 view dicts, each with keys: name, order, filter, sort. + List of 4 view dicts, each with keys: name, order, filter, sort. """ return [ { @@ -71,34 +71,11 @@ def build_base_views(domain: str) -> list[dict]: ], "filter": None, }, - { - "name": "推荐分析", - "order": [ - "year", - "title", - "first_author", - "journal", - "impact_factor", - "has_pdf", - "do_ocr", - "analyze", - "ocr_status", - "deep_reading_status", - "pdf_path", - "fulltext_md_path", - ], - "filter": "analyze = true AND has_pdf = true", - }, { "name": "待 OCR", "order": ["year", "first_author", "title", "has_pdf", "do_ocr", "ocr_status", "pdf_path"], "filter": "do_ocr = true AND ocr_status = 'pending'", }, - { - "name": "OCR 完成", - "order": ["year", "first_author", "title", "has_pdf", "do_ocr", "ocr_status", "pdf_path"], - "filter": "ocr_status = 'done'", - }, { "name": "待深度阅读", "order": [ @@ -115,51 +92,9 @@ def build_base_views(domain: str) -> list[dict]: "filter": "analyze = true AND ocr_status = 'done' AND deep_reading_status = 'pending'", }, { - "name": "深度阅读完成", - "order": [ - "year", - "first_author", - "title", - "has_pdf", - "do_ocr", - "analyze", - "ocr_status", - "deep_reading_status", - "pdf_path", - ], - "filter": "deep_reading_status = 'done'", - }, - { - "name": "正式卡片", - "order": [ - "title", - "year", - "first_author", - "journal", - "impact_factor", - "has_pdf", - "deep_reading_status", - "pdf_path", - ], - "filter": "deep_reading_status = 'done'", - }, - { - "name": "全记录", - "order": [ - "title", - "year", - "first_author", - "journal", - "impact_factor", - "has_pdf", - "do_ocr", - "analyze", - "ocr_status", - "deep_reading_status", - "pdf_path", - "fulltext_md_path", - ], - "filter": None, + "name": "重做OCR", + "order": ["year", "first_author", "title", "ocr_redo", "ocr_status"], + "filter": "ocr_status = 'done'", }, ] @@ -267,7 +202,7 @@ def merge_base_views(existing_content: str | None, new_views: list[dict], folder """Incrementally merge standard PaperForge views into an existing .base file. Strategy: - - PaperForge generates exactly 8 views with known names (STANDARD_VIEW_NAMES). + - PaperForge generates exactly 4 views with known names. - Any OTHER views in the existing file are user-defined and MUST be preserved. - Each PaperForge view is preceded by a PAPERFORGE_VIEW_PREFIX comment marker. - On refresh: replace ALL PaperForge views (identified by prefix) with fresh ones. @@ -276,7 +211,7 @@ def merge_base_views(existing_content: str | None, new_views: list[dict], folder Args: existing_content: Raw text of existing .base file (or None/empty for fresh generation). - new_views: List of 8 view dicts from build_base_views(). + new_views: List of 4 view dicts from build_base_views(). folder_filter: Vault-relative folder path for file.inFolder() (used for fresh generation). Returns: diff --git a/tests/test_base_preservation.py b/tests/test_base_preservation.py index 42128d7b..b3d9010e 100644 --- a/tests/test_base_preservation.py +++ b/tests/test_base_preservation.py @@ -43,7 +43,7 @@ class TestIncrementalMerge: ensure_base_views(self.vault, self.paths, self.config, force=False) content = domain_base.read_text(encoding="utf-8") - assert content.count("type: table") == 8 + assert content.count("type: table") == 4 user_custom = ( content @@ -61,9 +61,9 @@ class TestIncrementalMerge: refreshed = domain_base.read_text(encoding="utf-8") assert "My Custom Dashboard" in refreshed, "User custom view was lost on incremental refresh" - assert refreshed.count("type: table") == 9 + assert refreshed.count("type: table") == 5 assert "控制面板" in refreshed - assert "OCR 完成" in refreshed + assert "重做OCR" in refreshed def test_standard_paperforge_views_are_updated_on_refresh(self): """Standard views are replaced with fresh content on each refresh.""" @@ -71,12 +71,12 @@ class TestIncrementalMerge: ensure_base_views(self.vault, self.paths, self.config, force=False) content1 = domain_base.read_text(encoding="utf-8") - assert content1.count(PAPERFORGE_VIEW_PREFIX) == 8 + assert content1.count(PAPERFORGE_VIEW_PREFIX) == 4 ensure_base_views(self.vault, self.paths, self.config, force=False) content2 = domain_base.read_text(encoding="utf-8") - assert content2.count("type: table") == 8 + assert content2.count("type: table") == 4 assert "${LIBRARY_RECORDS}" not in content2 def test_force_flag_does_full_regeneration(self): @@ -101,8 +101,8 @@ class TestIncrementalMerge: refreshed = domain_base.read_text(encoding="utf-8") assert "My Custom View" not in refreshed, "force=True should have replaced all views" - assert refreshed.count("type: table") == 8 - assert refreshed.count(PAPERFORGE_VIEW_PREFIX) == 8 + assert refreshed.count("type: table") == 4 + assert refreshed.count(PAPERFORGE_VIEW_PREFIX) == 4 def test_user_modified_filter_on_standard_view_is_preserved(self): """User changes filter on a standard PF view — on refresh it is PRESERVED. @@ -140,7 +140,7 @@ class TestIncrementalMerge: assert domain_base.exists() content = domain_base.read_text(encoding="utf-8") - assert content.count("type: table") == 8 + assert content.count("type: table") == 4 assert PAPERFORGE_VIEW_PREFIX in content @@ -174,7 +174,7 @@ class TestLiteratureHubBase: hub_base = bases / "Literature Hub.base" assert hub_base.exists(), "Literature Hub.base not created" content = hub_base.read_text(encoding="utf-8") - assert content.count("type: table") == 8, f"Expected 8 views, got {content.count('type: table')}" + assert content.count("type: table") == 4, f"Expected 4 views, got {content.count('type: table')}" assert PAPERFORGE_VIEW_PREFIX in content assert "${LIBRARY_RECORDS}" not in content, "Placeholder should be substituted" @@ -237,8 +237,7 @@ views: result = merge_base_views(existing, views, folder_filter="Resources/Literature/骨科") assert "My Custom View" in result, "User view was lost" - assert "推荐分析" in result - assert result.count("type: table") >= 8 + assert result.count("type: table") >= 6 def test_merge_base_views_first_run_generates_fresh(self): """merge_base_views with no existing content generates fresh YAML.""" @@ -285,9 +284,9 @@ views: - file.name - title - year -# PAPERFORGE_VIEW: OCR 完成 +# PAPERFORGE_VIEW: 重做OCR - type: table - name: "OCR 完成" + name: "重做OCR" widths: year: 55 title: 380 diff --git a/tests/test_base_views.py b/tests/test_base_views.py index 9cf25a85..4af5f1fe 100644 --- a/tests/test_base_views.py +++ b/tests/test_base_views.py @@ -1,4 +1,4 @@ -"""Tests for the 8-view Base generation system (Phase 39: workflow-gate views).""" +"""Tests for the 4-view Base generation system (Phase 44: slim base views).""" from paperforge.worker.base_views import ( build_base_views, @@ -7,15 +7,22 @@ from paperforge.worker.base_views import ( class TestBuildBaseViews: - def test_returns_exactly_8_views(self): + def test_build_base_views_has_4_standard_views(self): views = build_base_views("骨科") - assert len(views) == 8 + names = {v["name"] for v in views} - def test_all_view_names_present(self): + assert names == {"控制面板", "待 OCR", "待深度阅读", "重做OCR"} + + def test_ocr_redo_view_has_correct_columns(self): views = build_base_views("骨科") - names = [v["name"] for v in views] - expected = ["控制面板", "推荐分析", "待 OCR", "OCR 完成", "待深度阅读", "深度阅读完成", "正式卡片", "全记录"] - assert names == expected + redo = next(v for v in views if v["name"] == "重做OCR") + + assert redo["order"] == ["year", "first_author", "title", "ocr_redo", "ocr_status"] + assert redo["filter"] == "ocr_status = 'done'" + + def test_returns_exactly_4_views(self): + views = build_base_views("骨科") + assert len(views) == 4 def test_each_view_has_required_keys(self): views = build_base_views("骨科") @@ -37,11 +44,6 @@ class TestBuildBaseViews: assert "do_ocr = true" in pending["filter"] assert "ocr_status = 'pending'" in pending["filter"] - def test_ocr_done_filter(self): - views = build_base_views("骨科") - done = next(v for v in views if v["name"] == "OCR 完成") - assert "ocr_status = 'done'" in done["filter"] - def test_deep_reading_pending_filter(self): views = build_base_views("骨科") pending = next(v for v in views if v["name"] == "待深度阅读") @@ -49,44 +51,16 @@ class TestBuildBaseViews: assert "ocr_status = 'done'" in pending["filter"] assert "deep_reading_status = 'pending'" in pending["filter"] - def test_build_base_views_includes_workflow_flags(self): + def test_removed_views_not_present(self): + removed = {"推荐分析", "OCR 完成", "深度阅读完成", "正式卡片", "全记录"} views = build_base_views("骨科") - for v in views: - order_set = set(v["order"]) - assert "has_pdf" in order_set, f"View '{v['name']}' missing has_pdf" - if v["name"] not in ("正式卡片",): - assert "do_ocr" in order_set, f"View '{v['name']}' missing do_ocr" - if v["name"] not in ("正式卡片", "待 OCR", "OCR 完成"): - assert "analyze" in order_set, f"View '{v['name']}' missing analyze" - - def test_build_base_views_removes_ghost_columns(self): - views = build_base_views("骨科") - ghost_fields = {"lifecycle", "maturity_level", "next_step"} - for v in views: - order_set = set(v["order"]) - assert ghost_fields.isdisjoint(order_set), f"View '{v['name']}' still contains ghost lifecycle columns" - - def test_build_base_views_filters_use_workflow_gates(self): - views = build_base_views("骨科") - for v in views: - if v["filter"] is not None: - assert "lifecycle" not in v["filter"], f"View '{v['name']}' filter uses lifecycle instead of workflow gates" + names = {v["name"] for v in views} + assert removed.isdisjoint(names), f"Removed views still present: {removed & names}" def test_build_base_views_has_no_sort(self): views = build_base_views("骨科") for v in views: - assert "sort" not in v, f"View '{v['name']}' should not have sort key (lifecycle removed from frontmatter)" - - def test_properties_yaml_updated(self): - from paperforge.worker.base_views import merge_base_views - fresh = merge_base_views(None, build_base_views("骨科"), folder_filter="Resources/Literature/骨科") - assert "lifecycle:" not in fresh - assert "maturity_level:" not in fresh - assert "next_step:" not in fresh - assert "has_pdf:" in fresh - assert "do_ocr:" in fresh - assert "analyze:" in fresh - assert "ocr_status:" in fresh + assert "sort" not in v, f"View '{v['name']}' should not have sort key" class TestSubstituteConfigPlaceholders: