mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
feat: slim base views to 4, add ocr redo view
This commit is contained in:
parent
c1c5b9b8ba
commit
d10c29bd96
3 changed files with 38 additions and 130 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue