diff --git a/backend/tests/test_import_and_model_errors.py b/backend/tests/test_import_and_model_errors.py index d0b1e1a..1e63df6 100644 --- a/backend/tests/test_import_and_model_errors.py +++ b/backend/tests/test_import_and_model_errors.py @@ -24,6 +24,8 @@ def test_scan_selected_student_folder_prefills_cover_identity(tmp_path, monkeypa assert project["status"] == "needs_confirmation" assert len(project["files"]) == 5 assert project["missing"] == [] + assert project["folder_identity"] == {"student_no": "", "name": "", "topic": ""} + assert all(file["detected_identity"]["student_no"] == "9000000001" for file in project["files"]) assert scan("test", root=tmp_path)["projects"] == result["projects"] @@ -39,6 +41,26 @@ def test_scan_conflicting_covers_need_confirmation(tmp_path, monkeypatch): assert project["student_no"] == "" assert project["warnings"] assert project["status"] == "needs_confirmation" + assert "学号" in project["warnings"][0] + assert "student_no" not in project["warnings"][0] + assert {file["detected_identity"]["student_no"] for file in project["files"]} == {"9000000001", "9000000002"} + + +def test_scan_unreadable_cover_has_no_stale_identity(tmp_path, monkeypatch): + from wisegrade import storage + folder = tmp_path / "9000000001_学生甲_机器人系统" + folder.mkdir() + (folder / "阶段报告1.pdf").write_bytes(b"fixture") + (folder / "阶段报告2.pdf").write_bytes(b"broken") + def read(path): + if path.name.endswith("2.pdf"): + raise ValueError("损坏PDF") + return "学号:9000000001\n姓名:学生甲\n题目:机器人系统" + monkeypatch.setattr(storage, "first_text", read) + project = scan(".", root=tmp_path)["projects"][0] + assert project["folder_identity"]["student_no"] == "9000000001" + assert project["files"][0]["detected_identity"]["student_no"] == "9000000001" + assert project["files"][1]["detected_identity"] == {} def test_scan_parent_skips_empty_folders(tmp_path, monkeypatch): diff --git a/backend/wisegrade/storage.py b/backend/wisegrade/storage.py index 2fc9611..ed92641 100644 --- a/backend/wisegrade/storage.py +++ b/backend/wisegrade/storage.py @@ -11,6 +11,7 @@ from .config import get_settings CATEGORIES = ("stage1", "stage2", "stage3", "final_report", "presentation") SUPPORTED = {".pdf", ".docx", ".pptx"} NAMES = {"stage1": "阶段报告1", "stage2": "阶段报告2", "stage3": "阶段报告3", "final_report": "最终报告", "presentation": "汇报PPT"} +IDENTITY_LABELS = {"student_no": "学号", "name": "姓名", "topic": "选题"} def secure_path(relative: str, *, root: Path | None = None, must_exist=True) -> Path: @@ -114,6 +115,7 @@ def scan(relative: str, *, root: Path | None = None): if item.is_dir() and not item.is_symlink() and has_materials(item)] for folder in folders: info = parse_student_folder(folder.name) + folder_identity = {key: info[key] for key in IDENTITY_LABELS} files = [] extracted = {} identity_values = {} @@ -124,6 +126,7 @@ def scan(relative: str, *, root: Path | None = None): if candidate.suffix.lower() not in SUPPORTED: continue error = "" + identity = {} try: text = first_text(candidate) category, status = classify(candidate, text) @@ -137,6 +140,7 @@ def scan(relative: str, *, root: Path | None = None): files.append({"relative_path": candidate.relative_to(base).as_posix(), "category": category, "status": status, "size": stat.st_size, "mtime": stat.st_mtime, "sha256": sha256(candidate), "mime_type": mimetypes.guess_type(candidate.name)[0] or "application/octet-stream", "error": error, + "detected_identity": identity, "first_text": text[:500] if not error else ""}) for category in CATEGORIES: matches = [file for file in files if file["category"] == category] @@ -145,17 +149,18 @@ def scan(relative: str, *, root: Path | None = None): file["status"] = "duplicate" for key, values in identity_values.items(): if len(values) > 1: - warnings.append(f"不同材料封面的{key}不一致,请核对后填写") + warnings.append(f"不同材料封面的{IDENTITY_LABELS[key]}不一致,请核对后填写") else: extracted[key] = next(iter(values)) for key, value in extracted.items(): if info.get(key) and value != info[key]: - warnings.append(f"封面{key}“{value}”与目录名“{info[key]}”不一致") + warnings.append(f"封面{IDENTITY_LABELS[key]}“{value}”与目录名“{info[key]}”不一致") elif not info.get(key): info[key] = value if warnings: info["status"] = "needs_confirmation" info.update({"relative_dir": folder.relative_to(base).as_posix(), "files": files, + "folder_identity": folder_identity, "detected_identity": extracted, "warnings": warnings, "missing": [category for category in CATEGORIES if not any(file["category"] == category for file in files)]}) projects.append(info)