"""Import parsing speed-ups: slim schema without model evidence (A) + sha256 parse cache (B).""" from __future__ import annotations import io from typing import Any from docx import Document from app.import_parser import ImportParseOutput, OpenAIResumeImportParser from app.resume_import_service import ResumeImportService class FakeCompletion: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] def complete(self, **kwargs: Any) -> Any: self.calls.append(kwargs) return kwargs["schema"].model_validate( { "basics": {"name": "张三"}, "sections": [ { "kind": "education", "heading": "教育经历", "items": [{"fields": {"school": "示例大学", "major": "软件工程"}}], } ], "skill_groups": [], } ) def _docx(text: str) -> bytes: document = Document() document.add_paragraph(text) buffer = io.BytesIO() document.save(buffer) return buffer.getvalue() def _service(tmp_path, completion: FakeCompletion) -> ResumeImportService: return ResumeImportService( storage_root=tmp_path / "imports", parser=OpenAIResumeImportParser(completion=completion), ) def test_service_uses_slim_schema_without_model_evidence(tmp_path) -> None: """模型不再逐条输出 evidence 引用(本地匹配已覆盖),输出 token 与时间同降。""" from app.import_parser_fast import SlimImportParseOutput completion = FakeCompletion() prepared = _service(tmp_path, completion).prepare( file_name="r.docx", declared_mime=None, content=_docx("张三 示例大学 软件工程") ) assert completion.calls[0]["schema"] is SlimImportParseOutput assert "evidence" not in completion.calls[0]["system_prompt"].casefold() assert completion.calls[0]["timeout_seconds"] == 45.0 assert completion.calls[0]["max_attempts"] == 1 assert prepared["document"]["sections"][0]["items"][0]["school"] == "示例大学" assert all(item["evidence"] for item in prepared["field_reviews"]) # 本地匹配仍然提供证据 def test_repeated_upload_of_same_file_skips_llm_parse(tmp_path) -> None: """同一文件跨会话重复上传命中 sha256 缓存,不再调 LLM 解析。""" completion = FakeCompletion() service = _service(tmp_path, completion) content = _docx("张三 示例大学 软件工程") first = service.prepare(file_name="a.docx", declared_mime=None, content=content) second = service.prepare(file_name="b.docx", declared_mime=None, content=content) assert len(completion.calls) == 1 assert second["document"] == first["document"] assert second["sha256"] == first["sha256"] def test_prepare_logs_timing_metadata_without_resume_content(tmp_path, monkeypatch) -> None: completion = FakeCompletion() events: list[dict[str, Any]] = [] monkeypatch.setattr( "app.resume_import_service.log_ai_event", lambda event, **fields: events.append({"event": event, **fields}), ) service = _service(tmp_path, completion) content = _docx("private resume text") service.prepare(file_name="resume.docx", declared_mime=None, content=content) service.prepare(file_name="resume-copy.docx", declared_mime=None, content=content) assert [event["event"] for event in events] == [ "resume_import_prepared", "resume_import_prepared", ] first, second = events for event in events: assert {"extract_ms", "parse_ms", "validate_ms", "storage_ms", "total_ms"} <= event.keys() assert event["file_extension"] == ".docx" assert event["size_bytes"] == len(content) assert "content" not in event assert "payload" not in event assert "private resume text" not in str(event) assert first["cache_hit"] is False assert first["text_characters"] > 0 assert second["cache_hit"] is True assert second["text_characters"] is None