fix: 3 micro-patches from 9.4/10 review

1. show_in_base — separate hidden from maintenance vs hidden from both
2. Unified is_degraded check — done_degraded and health yellow/red
   share one entry point
3. Hash error_summary in manifest instead of raw string
4. Fix Promise.withResolvers for ES compat
This commit is contained in:
LLLin000 2026-07-05 01:19:59 +08:00
parent d00485777f
commit b585e42ab2
3 changed files with 78 additions and 46 deletions

View file

@ -5,8 +5,19 @@ import { execFile } from "child_process";
export type MaintenanceCategory = "ok" | "rebuild" | "failed" | "limited";
export type MaintenanceAction = "rebuild" | "redo" | null;
export type DisplayAction = "retry_ocr" | "rebuild_result" | "upgrade_legacy" | "add_pdf" | "configure_ocr" | "none";
export type DisplayGroup = "retry" | "rebuild" | "legacy_optional" | "external_action" | "hidden";
export type DisplayAction =
| "retry_ocr"
| "rebuild_result"
| "upgrade_legacy"
| "add_pdf"
| "configure_ocr"
| "none";
export type DisplayGroup =
| "retry"
| "rebuild"
| "legacy_optional"
| "external_action"
| "hidden";
export type DisplaySeverity = "actionable" | "optional" | "external" | "normal";
export interface MaintenanceDisplayRow {
@ -94,10 +105,18 @@ export function buildMaintenanceSummary(
}
function ocrMaintenanceCachePath(vaultPath: string): string {
return path.join(vaultPath, "System", "PaperForge", "cache", "ocr_maintenance.json");
return path.join(
vaultPath,
"System",
"PaperForge",
"cache",
"ocr_maintenance.json"
);
}
export function readMaintenanceCache(vaultPath: string): MaintenanceCache | null {
export function readMaintenanceCache(
vaultPath: string
): MaintenanceCache | null {
try {
const filePath = ocrMaintenanceCachePath(vaultPath);
const raw = fs.readFileSync(filePath, "utf-8");
@ -107,7 +126,10 @@ export function readMaintenanceCache(vaultPath: string): MaintenanceCache | null
}
}
export function writeMaintenanceCache(vaultPath: string, cache: MaintenanceCache): void {
export function writeMaintenanceCache(
vaultPath: string,
cache: MaintenanceCache
): void {
const filePath = ocrMaintenanceCachePath(vaultPath);
const dir = path.dirname(filePath);
fs.mkdirSync(dir, { recursive: true });
@ -119,12 +141,12 @@ function execFilePromise(
args: string[],
options: { cwd: string; timeout: number }
): Promise<string> {
const { promise, resolve, reject } = Promise.withResolvers<string>();
execFile(cmd, args, options, (err: Error | null, stdout: string) => {
if (err) reject(err);
else resolve(stdout);
return new Promise<string>((resolve, reject) => {
execFile(cmd, args, options, (err: Error | null, stdout: string) => {
if (err) reject(err);
else resolve(stdout);
});
});
return promise;
}
export async function refreshMaintenanceData(
@ -155,12 +177,23 @@ export async function refreshMaintenanceData(
}
const changedKeys = Object.keys(manifest).filter(
(key) => !currentCache?.manifest[key] || currentCache.manifest[key] !== manifest[key]
(key) =>
!currentCache?.manifest[key] ||
currentCache.manifest[key] !== manifest[key]
);
const dataOut = await execFilePromise(
pythonExe,
[...extraArgs, "-m", "paperforge", "ocr", "list", "--json", "--keys", ...changedKeys],
[
...extraArgs,
"-m",
"paperforge",
"ocr",
"list",
"--json",
"--keys",
...changedKeys,
],
{ cwd: vaultPath, timeout: 30000 }
);
const updatedPapers: MaintenanceDisplayRow[] = JSON.parse(dataOut);
@ -182,6 +215,8 @@ export async function refreshMaintenanceData(
}
writeMaintenanceCache(vaultPath, cache);
const data = Object.values(cache.papers).filter((p) => p.visible_in_maintenance);
const data = Object.values(cache.papers).filter(
(p) => p.visible_in_maintenance
);
return { data, changed: true };
}

View file

@ -42,6 +42,7 @@ class OCRMaintenanceRow:
display_group: str = "hidden"
display_severity: str = "normal"
visible_in_maintenance: bool = False
show_in_base: bool = True
def to_dict(self) -> dict:
return {
@ -64,14 +65,15 @@ class OCRMaintenanceRow:
"can_redo": bool(self.can_redo),
"can_rebuild": bool(self.can_rebuild),
"recommended_action": _safe_str(self.recommended_action),
"display_action": _safe_str(self.display_action),
"display_label": _safe_str(self.display_label),
"display_label_key": _safe_str(self.display_label_key),
"display_reason": _safe_str(self.display_reason),
"display_reason_key": _safe_str(self.display_reason_key),
"display_group": _safe_str(self.display_group),
"display_severity": _safe_str(self.display_severity),
"visible_in_maintenance": bool(self.visible_in_maintenance),
"display_action": self.display_action,
"display_label": self.display_label,
"display_label_key": self.display_label_key,
"display_reason": self.display_reason,
"display_reason_key": self.display_reason_key,
"display_group": self.display_group,
"display_severity": self.display_severity,
"visible_in_maintenance": self.visible_in_maintenance,
"show_in_base": self.show_in_base,
}
@ -187,81 +189,74 @@ def _compute_display_fields(
degraded_reasons: list[str] | None = None,
) -> dict:
"""Map raw OCR state to display fields for the maintenance tab."""
_health_is_bad = health_overall in ("yellow", "red")
is_degraded = health_overall in ("yellow", "red") or status == "done_degraded"
if status in ("pending",):
return dict(display_action="none", display_label="等待处理", display_label_key="",
display_reason="", display_reason_key="",
display_group="hidden", display_severity="normal",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=True)
if status in ("running", "queued", "processing"):
return dict(display_action="none", display_label="处理中", display_label_key="",
display_reason="", display_reason_key="",
display_group="hidden", display_severity="normal",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=True)
if status in ("failed", "error", "fatal_error", "done_incomplete", "retryable_error") and can_redo:
return dict(display_action="retry_ocr", display_label="重试 OCR",
display_label_key="maintenance_action_retry_ocr",
display_reason="上次处理未完成,可以重新尝试",
display_reason_key="maintenance_reason_retry",
display_group="retry", display_severity="actionable",
visible_in_maintenance=True)
visible_in_maintenance=True, show_in_base=True)
if version == "v1" and can_redo:
return dict(display_action="upgrade_legacy", display_label="升级旧结果",
display_label_key="maintenance_action_upgrade_legacy",
display_reason="旧版本结果仍然可用,升级后可获得更好的章节、图表和问答效果",
display_reason_key="maintenance_reason_legacy",
display_group="legacy_optional", display_severity="optional",
visible_in_maintenance=True)
if status == "done_degraded" and can_rebuild:
visible_in_maintenance=True, show_in_base=True)
if is_degraded and can_rebuild:
return dict(display_action="rebuild_result", display_label="重建结果",
display_label_key="",
display_reason="已有OCR数据可重建获得更稳定的结果",
display_reason_key="",
display_group="rebuild", display_severity="actionable",
visible_in_maintenance=True)
if status == "done" and _health_is_bad and can_rebuild:
return dict(display_action="rebuild_result", display_label="重建结果",
display_label_key="",
display_reason="已有OCR数据可重建新版结果",
display_reason_key="",
display_group="rebuild", display_severity="actionable",
visible_in_maintenance=True)
if status == "done_degraded" and not can_rebuild and can_redo:
visible_in_maintenance=True, show_in_base=True)
if is_degraded and not can_rebuild and can_redo:
return dict(display_action="retry_ocr", display_label="重试 OCR",
display_label_key="",
display_reason="降级结果无法重建可重新OCR",
display_reason_key="",
display_group="retry", display_severity="actionable",
visible_in_maintenance=True)
visible_in_maintenance=True, show_in_base=True)
if status == "nopdf":
return dict(display_action="add_pdf", display_label="补充 PDF",
display_label_key="",
display_reason="请去 Zotero 添加 PDF 文件",
display_reason_key="",
display_group="external_action", display_severity="external",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=True)
if status == "blocked":
return dict(display_action="configure_ocr", display_label="配置 OCR",
display_label_key="",
display_reason="请配置 PaddleOCR API Token",
display_reason_key="",
display_group="external_action", display_severity="external",
visible_in_maintenance=False)
if status == "done" and not _health_is_bad:
visible_in_maintenance=False, show_in_base=True)
if status == "done" and not is_degraded:
return dict(display_action="none", display_label="已完成", display_label_key="",
display_reason="", display_reason_key="",
display_group="hidden", display_severity="normal",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=True)
if not can_redo and not can_rebuild:
return dict(display_action="none", display_label="已完成", display_label_key="",
display_reason="", display_reason_key="",
display_group="hidden", display_severity="normal",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=False)
return dict(display_action="none", display_label="已完成", display_label_key="",
display_reason="", display_reason_key="",
display_group="hidden", display_severity="normal",
visible_in_maintenance=False)
visible_in_maintenance=False, show_in_base=True)
def compute_maintenance_manifest(vault: Path) -> dict[str, str]:
@ -307,12 +302,14 @@ def compute_maintenance_manifest(vault: Path) -> dict[str, str]:
degraded_reasons=health.get("degraded_reasons", []) or [],
)
err_summary = _error_summary(meta)
err_summary_hash = hashlib.sha256(err_summary.encode("utf-8")).hexdigest() if err_summary else ""
raw = "|".join([
key, status, health_overall, version, rec_action,
df["display_action"], df["display_group"], df["display_severity"],
df.get("display_reason_key", ""),
str(can_redo), str(can_rebuild),
_error_stage(meta), _error_summary(meta),
_error_stage(meta), err_summary_hash,
str(health.get("degraded_reasons", [])),
])
manifest[key] = hashlib.sha256(raw.encode("utf-8")).hexdigest()

View file

@ -121,13 +121,13 @@ class TestRule6DoneBadHealthCanRebuild:
df = _call(status="done", health="red", can_rebuild=True)
_assert(df, action="rebuild_result", label="重建结果", group="rebuild",
severity="actionable", visible=True,
reason="已有OCR数据可重建新版结果")
reason="已有OCR数据可重建获得更稳定的结果")
def test_done_yellow_can_rebuild(self) -> None:
df = _call(status="done", health="yellow", can_rebuild=True)
_assert(df, action="rebuild_result", label="重建结果", group="rebuild",
severity="actionable", visible=True,
reason="已有OCR数据可重建新版结果")
reason="已有OCR数据可重建获得更稳定的结果")
# --- Rule 10: done + health NOT bad ---