# ============================================================ # 图纸工艺/工序/细节识别黄金测试(R71.2 增强) # 覆盖:合成文本识别、真实图纸标题栏/零件名、不误判比例、候选门禁。 # ============================================================ from __future__ import annotations from pathlib import Path from server.aps_domain.drawing_dxf import build_drawing_master_candidates, inspect_dxf from server.aps_domain.drawing_process import ( recognize_process_candidates, recognize_process_text, ) from server.aps_domain.drawing_understanding import build_drawing_understanding from tests.external_data import external_dir _RUIYANG_DIR = external_dir("RUIYANG_DEMO_DIR", "ruiyang") _DEFAULT_SAMPLE = _RUIYANG_DIR / "5060102101-001-e(1).dxf" _SAMPLE_003 = _RUIYANG_DIR / "5060102101-003-e(1).dxf" def _sample(path: Path) -> Path: if not path.is_file(): import pytest pytest.skip(f"real DXF fixture not available: {path}") return path def test_synthetic_process_and_detail_recognition(): parsed = { "asset": {"sha256": "0" * 64}, "texts": [ {"text": "DREHEN Ø20 TOLERANZ ±0.01", "evidence": {"source": "drawingText", "page": 1}}, {"text": "OBERFLÄCHE Ra 3.2", "evidence": {"source": "drawingText", "page": 1}}, {"text": "M30x1.5 16 TIEF", "evidence": {"source": "drawingText", "page": 1}}, {"text": "MONTAGE", "evidence": {"source": "drawingText", "page": 1}}, {"text": "SCALE 1:5", "evidence": {"source": "drawingText", "page": 1}}, ], } process = recognize_process_candidates(parsed) codes = {row["processCode"] for row in process["operations"]} assert {"TURNING", "ASSEMBLY", "THREADING"} <= codes assert "SURFACE_TREATMENT" in codes details = {row["field"]: row["value"] for row in process["details"]} assert details["tolerance"] == "±0.01" assert details["surfaceRoughness"] == "Ra 3.2" assert details["threadSpecification"] == "M30x1.5" assert details["depthMm"] == "16 TIEF" assert "M1" not in [row["value"] for row in process["details"]] def test_real_dxf_recognizes_process_and_details(): parsed = inspect_dxf(_sample(_DEFAULT_SAMPLE)) fields = {row["field"]: row["value"] for row in parsed["fieldCandidates"]} assert fields.get("threadSpecification") in {"M10", "M12", "M6", "M18x1.5"} assert "M1" not in [row["value"] for row in parsed["fieldCandidates"]] change_set = build_drawing_master_candidates(parsed) ops = change_set["routingOperations"] assert any(row["operationCode"] != "ENGINEERING_REVIEW" for row in ops) assert change_set["processDetails"] assert "processBlocks" in change_set assert "processAssemblies" in change_set assert "processLocations" in change_set assert "processSequenceSteps" in change_set for row in ops: assert row["reviewRequired"] is True assert row["standardTime"] is None assert row["resourceCode"] is None assert row["evidence"] def test_real_parts_list_extracts_assembly_thread_and_depth(): understanding = build_drawing_understanding(inspect_dxf(_sample(_SAMPLE_003))) assert understanding["processCandidates"] assert understanding["routingOperations"] details = {row["field"]: row["value"] for row in understanding["processDetails"]} assert details.get("threadSpecification") assert details.get("depthMm") assert any(row["processCode"] == "ASSEMBLY" for row in understanding["processCandidates"]) assert any(row["processCode"] == "THREADING" for row in understanding["processCandidates"]) _CHINESE_PROCESS_TEXT = """ 烟囱(9005)分段分为首面围壁组立、尾面围壁组立、侧壁组立、顶板组立等。 2.1首、尾围壁组立、侧壁组立、顶板组立在上建场地平胎位制作完成。 2.2烟囱分段在经托架改造的胎架上以首面围壁为胎,依次装配顶板组立、侧壁组立和尾面围壁组立,根据《9004分段建造工艺》要求穿插进行管系、舾装作业。 上建主体分段(9011、9012、9013、9014、9015、9016、9017分段)主要分为甲板组立、围堰双壳组立、外围壁组立、内部隔舱壁组立、伙食吊筒壁(仅9016分段)等组立。 3.1外围壁组立、内部隔舱壁组立在平胎位拼板,装焊骨材,形成平面组立。甲板板在分片制作场地拼板,然后甲板板上大组胎架进行拼板(单面焊)、划线、装焊骨材、横梁、纵桁等形成甲板组立。伙食吊筒壁在联合厂房进行下料、卷制、焊接。 3.2围堰双壳组立内的板片、隔板、顶板、骨材、筋板肘板等在联合厂房下料送至上建场地。在上建场地以一侧围板为胎装焊内部构件,翻身与另一侧围板装焊形成完整的围堰双壳组立。 9212/9312围堰分段的建造流程与3.2项类似。 9211/9311/9411/9412/9512分段在上建场地以甲板为胎反造。 9216/9316分段在上建场地制作成型。分段主要包含驾驶甲板平台组立、FR46横壁组立、箱形立柱和斜纵壁等组立。 6.2驾驶甲板平台中组立先以驾驶甲板为基装配隔板组立,完成焊接后翻身装配到驾驶甲板平台组立上,再装配FR46舷墙组立。 """ def test_chinese_process_text_extracts_rich_key_information(): result = recognize_process_text(_CHINESE_PROCESS_TEXT) codes = {row["blockCode"] for row in result["blocks"]} assert "9005" in codes assert any("9011" in code for code in codes) assert any("9212/9312" in code or "9212" in code for code in codes) assert any("9211/9311/9411/9412/9512" in code or "9211" in code for code in codes) assert any("9216/9316" in code or "9216" in code for code in codes) assert any("FR46" in code for code in codes) assemblies = {row["assemblyName"] for row in result["assemblies"]} assert {"首面围壁组立", "尾面围壁组立", "侧壁组立", "顶板组立", "甲板组立", "围堰双壳组立", "外围壁组立", "内部隔舱壁组立", "驾驶甲板平台组立"} <= assemblies assert any("FR46横壁组立" in name for name in assemblies) ops = {row["processName"] for row in result["operations"]} assert {"拼板", "装焊", "下料", "卷制", "翻身", "反造", "舾装", "管系", "焊接", "划线"} <= ops locations = {row["location"] for row in result["locations"]} assert {"上建场地", "联合厂房", "平胎位", "大组胎架"} <= locations assert result["sequenceSteps"] assert result["status"] == "PENDING_REVIEW" def test_process_candidates_stay_pending_with_evidence(): understanding = build_drawing_understanding(inspect_dxf(_sample(_SAMPLE_003))) for row in understanding["processCandidates"] + understanding["processDetails"]: assert row["status"] == "PENDING_REVIEW" assert row["reviewRequired"] is True assert 0 < row["confidence"] <= 1 assert row["evidence"]