{"id":7847,"date":"2026-03-05T00:08:39","date_gmt":"2026-03-04T16:08:39","guid":{"rendered":"https:\/\/www.xinyixx.com\/?p=7847"},"modified":"2026-03-05T00:08:39","modified_gmt":"2026-03-04T16:08:39","slug":"design","status":"publish","type":"post","link":"https:\/\/www.xinyixx.com\/index.php\/2026\/03\/05\/design\/","title":{"rendered":"\u5efa\u7b51\u89c4\u8303\u81ea\u52a8\u6821\u9a8c\u7cfb\u7edf"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><strong>\u4e00\u3001\u7cfb\u7edf\u603b\u4f53\u8bbe\u8ba1<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1.1 &nbsp;\u8bbe\u8ba1\u54f2\u5b66<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7b51\u89c4\u8303\u6821\u9a8c\u7cfb\u7edf\u7684\u672c\u8d28\u662f\u6761\u4ef6\u5224\u65ad\u5f15\u64ce\uff0c\u800c\u975e\u751f\u6210\u5f0f AI \u5e94\u7528\u3002\u8fd9\u4e00\u5b9a\u4f4d\u51b3\u5b9a\u4e86\u6574\u4e2a\u7cfb\u7edf\u7684\u6280\u672f\u9009\u578b\u903b\u8f91\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u89c4\u5219\u5f15\u64ce\u8d1f\u8d23\u5224\u65ad\u5bf9\u9519\uff08\u786e\u5b9a\u6027\u3001\u53ef\u8ffd\u6eaf\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5927\u6a21\u578b\u8d1f\u8d23\u89e3\u91ca\u539f\u56e0\uff08\u81ea\u7136\u8bed\u8a00\u8868\u8fbe\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5de5\u4f5c\u6d41\u8d1f\u8d23\u4e32\u8054\u6d41\u7a0b\uff08\u81ea\u52a8\u5316\u3001\u53ef\u76d1\u63a7\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u7cfb\u7edf\u5206\u4e94\u5c42\uff0c\u6bcf\u5c42\u804c\u8d23\u5355\u4e00\uff0c\u5c42\u95f4\u901a\u8fc7\u6807\u51c6\u5316\u63a5\u53e3\u901a\u4fe1\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 \u67b6\u6784\u603b\u89c8<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2502 &nbsp;&nbsp;\u7b2c\u4e94\u5c42\uff1a\u5de5\u4f5c\u6d41\u81ea\u52a8\u5316 (n8n) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2502 &nbsp;&nbsp;\u7b2c\u56db\u5c42\uff1aAI \u89e3\u91ca\u5c42 (Dify \/ LLM) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2502 &nbsp;&nbsp;\u7b2c\u4e09\u5c42\uff1a\u89c4\u5219\u5f15\u64ce (Python DSL + YAML) &nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2502 &nbsp;&nbsp;\u7b2c\u4e8c\u5c42\uff1a\u53c2\u6570\u62bd\u53d6\u5c42 (IFC Parser) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2502 &nbsp;&nbsp;\u7b2c\u4e00\u5c42\uff1a\u6570\u636e\u8f93\u5165\u5c42 (Revit\/CAD\/\u624b\u52a8\u5f55\u5165) \u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1.2 &nbsp;\u6280\u672f\u6808\u9009\u578b<\/strong><strong><\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>\u5c42\u7ea7<\/strong><\/td><td><strong>\u6280\u672f\u9009\u578b<\/strong><\/td><td><strong>\u9009\u578b\u7406\u7531<\/strong><\/td><\/tr><tr><td>\u8f93\u5165\u5c42<\/td><td>Revit API \/ IFC \/ REST<\/td><td>BIM \u884c\u4e1a\u6807\u51c6\uff0c\u6570\u636e\u7ed3\u6784\u5316\u7a0b\u5ea6\u9ad8<\/td><\/tr><tr><td>\u62bd\u53d6\u5c42<\/td><td>Python + ifcopenshell<\/td><td>\u6210\u719f\u7684 IFC \u89e3\u6790\u5e93\uff0c\u793e\u533a\u6d3b\u8dc3<\/td><\/tr><tr><td>\u89c4\u5219\u5f15\u64ce<\/td><td>Python + YAML DSL<\/td><td>\u53ef\u8bfb\u6027\u5f3a\uff0c\u89c4\u5219\u7248\u672c\u5316\u7ba1\u7406\u4fbf\u6377<\/td><\/tr><tr><td>AI \u89e3\u91ca\u5c42<\/td><td>Dify + GPT-4 \/ Claude<\/td><td>\u63a5\u53e3\u6210\u719f\uff0cprompt \u5de5\u7a0b\u53ef\u63a7<\/td><\/tr><tr><td>\u5de5\u4f5c\u6d41<\/td><td>n8n<\/td><td>\u5f00\u6e90\u3001\u53ef\u79c1\u6709\u5316\u90e8\u7f72<\/td><\/tr><tr><td>\u6570\u636e\u5e93<\/td><td>PostgreSQL + Redis<\/td><td>\u5173\u7cfb\u578b\u5b58\u89c4\u5219\uff0cRedis \u7f13\u5b58\u7ed3\u679c<\/td><\/tr><tr><td>\u540e\u7aef API<\/td><td>FastAPI (Python)<\/td><td>\u5f02\u6b65\u6027\u80fd\u597d\uff0c\u6587\u6863\u81ea\u52a8\u751f\u6210<\/td><\/tr><tr><td>\u524d\u7aef<\/td><td>React + Ant Design<\/td><td>\u7ec4\u4ef6\u5e93\u5b8c\u5584\uff0c\u9002\u5408\u6570\u636e\u5bc6\u96c6\u754c\u9762<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1.3 &nbsp;\u6838\u5fc3\u6570\u636e\u6d41<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 \u6570\u636e\u6d41<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">BIM\u6587\u4ef6 (IFC)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u53c2\u6570\u62bd\u53d6\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba JSON \u53c2\u6570\u5305<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u89c4\u5219\u5f15\u64ce\u52a0\u8f7d\u5668<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u9010\u6761\u89c4\u5219\u5224\u65ad<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u6821\u9a8c\u7ed3\u679c\u5217\u8868 [ {rule_id, status, value, threshold} ]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AI \u89e3\u91ca\u5c42<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u2502<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u25bc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PDF\/Word \u62a5\u544a &nbsp;+ &nbsp;\u63a8\u9001\u901a\u77e5<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u4e8c\u3001\u7b2c\u4e00\u5c42\uff1a\u6570\u636e\u8f93\u5165\u5c42<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2.1 &nbsp;BIM \u6a21\u578b\u63a5\u5165\uff08\u4e3b\u8def\u5f84\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Revit \u5bfc\u51fa IFC \u662f\u6700\u4f18\u5148\u7684\u8f93\u5165\u65b9\u5f0f\u3002\u9700\u8981\u5728 Revit \u4e2d\u914d\u7f6e\u5bfc\u51fa\u53c2\u6570\uff0c\u786e\u4fdd\u5173\u952e\u5143\u7d20\u88ab\u6b63\u786e\u6807\u8bb0\u3002\u4ee5\u4e0b\u4e3a Revit Python Shell \/ Dynamo \u5bfc\u51fa\u811a\u672c\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (Revit API)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># revit_exporter.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import clr<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">clr.AddReference(&#8216;RevitAPI&#8217;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from Autodesk.Revit.DB import IFCExportOptions, IFCVersion, Transaction<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def export_to_ifc(doc, output_path):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options = IFCExportOptions()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.FileVersion = IFCVersion.IFC2x3CV2<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.ExportBaseQuantities = True &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# \u5bfc\u51fa\u57fa\u7840\u5de5\u7a0b\u91cf<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.WallAndColumnSplitting = True &nbsp;&nbsp;&nbsp;&nbsp;# \u5899\u67f1\u6309\u697c\u5c42\u62c6\u5206<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.SpaceBoundaryLevel = 1 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# \u7a7a\u95f4\u8fb9\u754c\uff08\u9632\u706b\u5206\u533a\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.ExportInternalRevitPropertySets = True<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.ExportUserDefinedPsets = True<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ifc_options.ExportUserDefinedPsetsFileName = &#8220;fire_safety_psets.txt&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;transaction = Transaction(doc, &#8220;Export IFC&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;transaction.Start()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;try:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;doc.Export(output_path, &#8220;model.ifc&#8221;, ifc_options)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;transaction.Commit()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(f&#8221;IFC \u5bfc\u51fa\u6210\u529f: {output_path}\/model.ifc&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;except Exception as e:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;transaction.RollBack()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RuntimeError(f&#8221;\u5bfc\u51fa\u5931\u8d25: {str(e)}&#8221;)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2.2 &nbsp;IFC \u6587\u4ef6\u4e0a\u4f20\u63a5\u53e3\uff08FastAPI\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (FastAPI)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># api\/upload.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from fastapi import APIRouter, UploadFile, File, BackgroundTasks, HTTPException<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from pathlib import Path<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import uuid, aiofiles<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">router = APIRouter(prefix=&#8221;\/api\/v1&#8243;, tags=[&#8220;upload&#8221;])<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">UPLOAD_DIR = Path(&#8220;uploads&#8221;); UPLOAD_DIR.mkdir(exist_ok=True)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@router.post(&#8220;\/projects\/{project_id}\/upload&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">async def upload_ifc(project_id: str, background_tasks: BackgroundTasks,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;file: UploadFile = File(&#8230;)):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if not file.filename.endswith((&#8216;.ifc&#8217;, &#8216;.ifczip&#8217;)):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise HTTPException(400, &#8220;\u4ec5\u652f\u6301 .ifc \u6216 .ifczip \u683c\u5f0f&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;content = await file.read()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if len(content) &gt; 500 * 1024 * 1024:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise HTTPException(413, &#8220;\u6587\u4ef6\u8d85\u8fc7 500MB \u9650\u5236&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;file_id = str(uuid.uuid4())<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;async with aiofiles.open(UPLOAD_DIR \/ f&#8221;{file_id}.ifc&#8221;, &#8216;wb&#8217;) as f:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;await f.write(content)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;background_tasks.add_task(trigger_parse_pipeline,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;project_id, file_id, str(UPLOAD_DIR \/ f&#8221;{file_id}.ifc&#8221;))<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return {&#8220;file_id&#8221;: file_id, &#8220;status&#8221;: &#8220;processing&#8221;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">async def trigger_parse_pipeline(project_id, file_id, file_path):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;from services.parser import IFCParser<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;from services.rule_engine import RuleEngine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;parser = IFCParser(file_path)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;params &nbsp;= parser.extract_all()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;engine &nbsp;= RuleEngine()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;results = engine.run(params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;await save_results(project_id, file_id, results)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;await update_status(file_id, &#8220;completed&#8221;)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2.3 &nbsp;\u624b\u52a8\u53c2\u6570\u5f55\u5165\uff08\u5907\u7528\u8def\u5f84\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5f53\u9879\u76ee\u6ca1\u6709 BIM \u6a21\u578b\u65f6\uff0c\u63d0\u4f9b\u7ed3\u6784\u5316 Pydantic \u6a21\u578b\u8fdb\u884c\u53c2\u6570\u5f55\u5165\uff0c\u540c\u6837\u80fd\u8fdb\u5165\u540e\u7eed\u89c4\u5219\u5f15\u64ce\u6d41\u7a0b\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (Pydantic)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># api\/manual_input.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from pydantic import BaseModel, Field, validator<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from typing import Optional, List<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from enum import Enum<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class BuildingType(str, Enum):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;RESIDENTIAL_HIGH = &#8220;\u9ad8\u5c42\u4f4f\u5b85&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;OFFICE &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= &#8220;\u529e\u516c\u5efa\u7b51&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;COMMERCIAL &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= &#8220;\u5546\u4e1a\u5efa\u7b51&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;HOSPITAL &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= &#8220;\u533b\u7597\u5efa\u7b51&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class FireZone(BaseModel):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;zone_id: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;area: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float = Field(&#8230;, gt=0, &nbsp;description=&#8221;\u9632\u706b\u5206\u533a\u9762\u79ef\uff08\u33a1\uff09&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int &nbsp;&nbsp;= Field(&#8230;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description=&#8221;\u6240\u5728\u697c\u5c42&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;has_sprinkler: bool &nbsp;= Field(False, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description=&#8221;\u662f\u5426\u8bbe\u7f6e\u81ea\u52a8\u55b7\u6c34\u706d\u706b\u7cfb\u7edf&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class StairInfo(BaseModel):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stair_id: &nbsp;&nbsp;&nbsp;&nbsp;str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;net_width: &nbsp;&nbsp;&nbsp;float &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= Field(&#8230;, gt=0, description=&#8221;\u697c\u68af\u51c0\u5bbd\uff08m\uff09&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stair_type: &nbsp;&nbsp;str &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= Field(&#8230;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description=&#8221;\u5c01\u95ed\/\u9632\u70df\/\u655e\u5f00&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;serves_floors: List[int] = Field(&#8230;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description=&#8221;\u670d\u52a1\u697c\u5c42\u5217\u8868&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class BuildingParams(BaseModel):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_name: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building_type: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BuildingType<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;total_area: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float = Field(&#8230;, gt=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building_height: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float = Field(&#8230;, gt=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor_count: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int &nbsp;&nbsp;= Field(&#8230;, gt=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;basement_count: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int &nbsp;&nbsp;= Field(0, &nbsp;ge=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;fire_zones: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List[FireZone] &nbsp;&nbsp;= Field(&#8230;, min_items=1)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stairs: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List[StairInfo] &nbsp;= Field(&#8230;, min_items=1)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;exit_door_count: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int &nbsp;&nbsp;= Field(&#8230;, gt=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;max_evacuation_distance: &nbsp;float = Field(&#8230;, gt=0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;min_sunshine_hours: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Optional[float] &nbsp;= None<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u4e09\u3001\u7b2c\u4e8c\u5c42\uff1a\u53c2\u6570\u62bd\u53d6\u5c42<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3.1 &nbsp;IFCParser \u6838\u5fc3\u7c7b<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">IFCParser \u662f\u6574\u4e2a\u7cfb\u7edf\u7684\u6570\u636e\u7f51\u5173\uff0c\u8d1f\u8d23\u5c06\u975e\u7ed3\u6784\u5316\u7684 IFC \u6a21\u578b\u8f6c\u6362\u4e3a\u89c4\u5219\u5f15\u64ce\u6240\u9700\u7684\u6807\u51c6\u5316\u53c2\u6570\u5305 BuildingParameters\u3002\u6240\u6709\u4e0b\u6e38\u6a21\u5757\u5747\u6d88\u8d39\u8fd9\u4efd JSON \u683c\u5f0f\u7684\u53c2\u6570\u5305\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># services\/parser.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import ifcopenshell<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import ifcopenshell.geom<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from dataclasses import dataclass, asdict<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from typing import Dict, List, Optional<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class FireZoneData:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;zone_id: str; &nbsp;area: float; &nbsp;floor: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor_name: str; &nbsp;has_sprinkler: bool<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class StairData:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stair_id: str; &nbsp;net_width: float; &nbsp;stair_type: str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;bottom_floor: int; &nbsp;top_floor: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class BuildingParameters:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_name: str; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ifc_source: str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building_type: str; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;building_height: float<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor_count: int; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;basement_count: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;total_area: float; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;footprint_area: float<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;fire_zones: List[FireZoneData]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;max_fire_zone_area: float<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stairs: List[StairData]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;stair_count: int; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;min_stair_width: float<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;exit_door_count: int; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max_evacuation_distance: float<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;min_sunshine_hours: Optional[float]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;has_elevator: bool; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;has_refuge_floor: bool<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3.2 &nbsp;\u5efa\u7b51\u9ad8\u5ea6\u63d0\u53d6\u903b\u8f91<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7b51\u9ad8\u5ea6\u662f\u89c4\u5219\u89e6\u53d1\u7684\u6838\u5fc3\u53c2\u6570\uff0c\u91c7\u7528\u4e24\u9636\u6bb5\u83b7\u53d6\u7b56\u7565\uff1a\u4f18\u5148\u4ece IFC \u5c5e\u6027\u96c6\u8bfb\u53d6\u7cbe\u786e\u503c\uff0c\u5931\u8d25\u65f6\u4ece\u6700\u9ad8\u697c\u5c42\u6807\u9ad8\u52a0\u4f30\u7b97\u5c42\u9ad8\u63a8\u7b97\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def _get_building_height(self) -&gt; float:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;buildings = self.model.by_type(&#8220;IfcBuilding&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if not buildings:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building = buildings[0]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u7b56\u75651\uff1a\u4ece\u5c5e\u6027\u96c6\u8bfb\u53d6<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;for rel in building.IsDefinedBy:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if rel.is_a(&#8220;IfcRelDefinesByProperties&#8221;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pset = rel.RelatingPropertyDefinition<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if hasattr(pset, &#8216;Name&#8217;) and &#8216;Height&#8217; in (pset.Name or &#8221;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for prop in pset.HasProperties:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if &#8216;Height&#8217; in prop.Name:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return float(prop.NominalValue.wrappedValue)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u7b56\u75652\uff1a\u4ece\u697c\u5c42\u6807\u9ad8\u63a8\u7b97<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;storeys = self.model.by_type(&#8220;IfcBuildingStorey&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;elevations = [s.Elevation for s in storeys if s.Elevation is not None]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;positive = [e for e in elevations if e &gt;= 0]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if positive:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return max(positive) + 3.6 &nbsp;&nbsp;# \u4f30\u7b97\u9876\u5c42\u5c42\u9ad8<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return 0.0<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3.3 &nbsp;\u9632\u706b\u5206\u533a\u63d0\u53d6\u903b\u8f91<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u9632\u706b\u5206\u533a\u63d0\u53d6\u91c7\u7528\u53cc\u7b56\u7565\uff1a\u4f18\u5148\u89e3\u6790 IfcZone \u4e2d\u660e\u786e\u6807\u8bb0\u7684\u9632\u706b\u5206\u533a\uff1b\u82e5 BIM \u5efa\u6a21\u4e0d\u89c4\u8303\uff08\u672a\u5efa\u9632\u706b\u5206\u533a\uff09\uff0c\u5219\u9000\u800c\u6309\u697c\u5c42\u805a\u5408 IfcSpace \u9762\u79ef\u4f5c\u4e3a\u8fd1\u4f3c\u503c\uff0c\u5e76\u5728\u62a5\u544a\u4e2d\u6807\u8bb0\u4e3a\u3010\u9700\u4eba\u5de5\u786e\u8ba4\u3011\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def _extract_fire_zones(self) -&gt; List[FireZoneData]:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;zones = []<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u7b56\u75651\uff1a\u89e3\u6790\u660e\u786e\u6807\u8bb0\u7684 IfcZone<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;for zone in self.model.by_type(&#8220;IfcZone&#8221;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if zone.Name and (&#8216;\u9632\u706b&#8217; in zone.Name or &#8216;Fire&#8217; in zone.Name):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zones.append(FireZoneData(<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zone_id=zone.GlobalId,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;area=self._get_zone_area(zone),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;floor=self._get_zone_floor(zone),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;floor_name=zone.Name,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;has_sprinkler=self._zone_has_sprinkler(zone),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;))<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u7b56\u75652\uff1a\u6309\u697c\u5c42\u805a\u5408 IfcSpace\uff08\u6a21\u578b\u672a\u5efa\u9632\u706b\u5206\u533a\u65f6\u4f7f\u7528\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if not zones:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zones = self._aggregate_spaces_by_floor()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return zones<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def _aggregate_spaces_by_floor(self) -&gt; List[FireZoneData]:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor_areas: Dict[int, float] = {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;for space in self.model.by_type(&#8220;IfcSpace&#8221;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;floor_num = self._get_element_floor(space)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;area = self._get_space_area(space)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;floor_areas[floor_num] = floor_areas.get(floor_num, 0) + area<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FireZoneData(zone_id=f&#8221;floor_{n}&#8221;, area=a, floor=n,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;floor_name=f&#8221;\u7b2c{n}\u5c42\uff08\u805a\u5408\uff09&#8221;, has_sprinkler=False)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for n, a in sorted(floor_areas.items()) if n &gt;= 0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;]<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u56db\u3001\u7b2c\u4e09\u5c42\uff1a\u89c4\u5219\u5f15\u64ce\uff08\u6838\u5fc3\uff09<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4.1 &nbsp;\u89c4\u5219 DSL \u7ed3\u6784\u8bbe\u8ba1<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6bcf\u6761\u89c4\u5219\u7531\u4ee5\u4e0b\u5b57\u6bb5\u6784\u6210\uff0c\u5168\u90e8\u4ee5 YAML \u683c\u5f0f\u5b58\u50a8\uff0c\u652f\u6301 Git \u7248\u672c\u7ba1\u7406\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>\u5b57\u6bb5<\/strong><\/td><td><strong>\u7c7b\u578b<\/strong><\/td><td><strong>\u542b\u4e49<\/strong><\/td><\/tr><tr><td>id<\/td><td>string<\/td><td>\u89c4\u5219\u552f\u4e00\u6807\u8bc6\uff0c\u5982 FIRE_001<\/td><\/tr><tr><td>name<\/td><td>string<\/td><td>\u89c4\u5219\u540d\u79f0<\/td><\/tr><tr><td>standard_ref<\/td><td>string<\/td><td>\u89c4\u8303\u6761\u6587\u7f16\u53f7\uff0c\u5982 GB50016-2014 \u7b2c5.5.25\u6761<\/td><\/tr><tr><td>severity<\/td><td>enum<\/td><td>\u4e25\u91cd \/ \u4e00\u822c \/ \u63d0\u793a<\/td><\/tr><tr><td>apply_when<\/td><td>list<\/td><td>\u9002\u7528\u6761\u4ef6\uff08AND \u903b\u8f91\uff09\uff0c\u4e0d\u6ee1\u8db3\u5219\u6807\u8bb0 not_applicable<\/td><\/tr><tr><td>check<\/td><td>object<\/td><td>\u6821\u9a8c\u903b\u8f91\uff0c\u652f\u6301 single\uff08\u5355\u503c\uff09\u548c foreach\uff08\u9010\u9879\uff09<\/td><\/tr><tr><td>fail_message<\/td><td>template<\/td><td>\u4e0d\u5408\u89c4\u63d0\u793a\u6a21\u677f\uff0c\u652f\u6301 {field} \u53d8\u91cf\u63d2\u503c<\/td><\/tr><tr><td>suggestion<\/td><td>string<\/td><td>\u6574\u6539\u5efa\u8bae<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4.2 &nbsp;\u89c4\u5219 YAML \u793a\u4f8b\uff08\u9632\u706b\u89c4\u8303\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 YAML (\u89c4\u5219\u5b9a\u4e49)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># rules\/fire_safety.yaml<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">metadata:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;standard: &#8220;GB 50016-2014&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;version: &nbsp;&#8220;2018&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">rules:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8211; id: FIRE_001<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;name: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u9ad8\u5c42\u5efa\u7b51\u5b89\u5168\u51fa\u53e3\u6570\u91cf&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;standard_ref: &#8220;GB50016-2014 \u7b2c5.5.25\u6761&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;category: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u758f\u6563\u8bbe\u65bd&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;severity: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u4e25\u91cd&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;apply_when:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_height, operator: &#8220;&gt;&#8221;, &nbsp;value: 27 &nbsp;&nbsp;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_type, &nbsp;&nbsp;operator: &#8220;in&#8221;, value: [&#8220;\u9ad8\u5c42\u4f4f\u5b85&#8221;,&#8221;\u4f4f\u5b85\u5efa\u7b51&#8221;] }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;field: &nbsp;&nbsp;&nbsp;exit_door_count<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;operator: &#8220;&gt;=&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value: &nbsp;&nbsp;&nbsp;2<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;fail_message: &gt;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u5efa\u7b51\u9ad8\u5ea6\u4e3a{building_height}m\uff0c\u8d85\u8fc727m\uff0c\u5c5e\u4e8e\u9ad8\u5c42\u4f4f\u5b85\uff0c<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u8981\u6c42\u5b89\u5168\u51fa\u53e3\u4e0d\u5c11\u4e8e2\u4e2a\uff0c\u5f53\u524d\u4ec5\u6709{exit_door_count}\u4e2a\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;suggestion: &#8220;\u589e\u52a0\u5b89\u5168\u51fa\u53e3\uff0c\u6216\u6838\u67e5\u662f\u5426\u7b26\u5408\u53ef\u8bbe\u7f6e1\u4e2a\u5b89\u5168\u51fa\u53e3\u7684\u4f8b\u5916\u6761\u6b3e&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8211; id: FIRE_002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;name: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u9632\u706b\u5206\u533a\u9762\u79ef\uff08\u9ad8\u5c42\u516c\u5171\u5efa\u7b51\uff09&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;standard_ref: &#8220;GB50016-2014 \u88685.3.1&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;category: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u9632\u706b\u5206\u533a&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;severity: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u4e25\u91cd&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;apply_when:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_height, operator: &#8220;&gt;&#8221;, &nbsp;value: 50 }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_type, &nbsp;&nbsp;operator: &#8220;in&#8221;, value: [&#8220;\u529e\u516c\u5efa\u7b51&#8221;,&#8221;\u5546\u4e1a\u5efa\u7b51&#8221;] }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type: &nbsp;&nbsp;&nbsp;&#8220;foreach&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;iterate: &#8220;fire_zones&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;condition:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: has_sprinkler, operator: &#8220;==&#8221;, value: false,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;check_field: area, operator: &#8220;&lt;=&#8221;, check_value: 1000 }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;condition_alt:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: has_sprinkler, operator: &#8220;==&#8221;, value: true,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;check_field: area, operator: &#8220;&lt;=&#8221;, check_value: 2000 }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;fail_message: &gt;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u9632\u706b\u5206\u533a {zone_id}\uff08\u7b2c{floor}\u5c42\uff09\u9762\u79ef {area}\u33a1 \u8d85\u8fc7\u5141\u8bb8\u503c {threshold}\u33a1\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8211; id: FIRE_003<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;name: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u758f\u6563\u697c\u68af\u51c0\u5bbd\u5ea6\uff08\u9ad8\u5c42\u516c\u5171\u5efa\u7b51\uff09&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;standard_ref: &#8220;GB50016-2014 \u7b2c5.5.18\u6761&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;severity: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u4e25\u91cd&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;apply_when:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_height, operator: &#8220;&gt;&#8221;, value: 24 }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type: &#8220;foreach&#8221;; &nbsp;iterate: &#8220;stairs&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;check_field: net_width; &nbsp;operator: &#8220;&gt;=&#8221;; &nbsp;check_value: 1.2<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8211; id: FIRE_005<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;name: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u8d85\u9ad8\u5c42\u5efa\u7b51\u907f\u96be\u5c42&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;standard_ref: &#8220;GB50016-2014 \u7b2c5.5.23\u6761&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;severity: &nbsp;&nbsp;&nbsp;&nbsp;&#8220;\u4e25\u91cd&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;apply_when:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_height, operator: &#8220;&gt;&#8221;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value: 100 }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; { field: building_type, &nbsp;&nbsp;operator: &#8220;not_in&#8221;, value: [&#8220;\u9ad8\u5c42\u4f4f\u5b85&#8221;,&#8221;\u4f4f\u5b85\u5efa\u7b51&#8221;] }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;field: has_refuge_floor; &nbsp;operator: &#8220;==&#8221;; &nbsp;value: true<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;fail_message: &gt;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\u5efa\u7b51\u9ad8\u5ea6 {building_height}m \u8d85\u8fc7100m\uff0c\u5fc5\u987b\u8bbe\u7f6e\u907f\u96be\u5c42\uff0c\u5f53\u524d\u672a\u68c0\u6d4b\u5230\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4.3 &nbsp;\u89c4\u5219\u5f15\u64ce\u6267\u884c\u5668<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u89c4\u5219\u5f15\u64ce\u7684\u6838\u5fc3\u662f RuleEngine \u7c7b\u548c ConditionEvaluator \u7c7b\u3002\u524d\u8005\u8d1f\u8d23\u52a0\u8f7d\u89c4\u5219\u3001\u904d\u5386\u6267\u884c\uff1b\u540e\u8005\u8d1f\u8d23\u5355\u6761\u8868\u8fbe\u5f0f\u6c42\u503c\uff0c\u652f\u6301\u591a\u79cd\u6bd4\u8f83\u8fd0\u7b97\u7b26\u548c\u5d4c\u5957\u5b57\u6bb5\u8bbf\u95ee\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (\u89c4\u5219\u5f15\u64ce\u6838\u5fc3)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># services\/rule_engine.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import yaml<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from pathlib import Path<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from dataclasses import dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from typing import List, Dict, Any, Optional<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class RuleResult:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;rule_id: str; &nbsp;&nbsp;&nbsp;rule_name: str; &nbsp;&nbsp;&nbsp;standard_ref: str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;category: str; &nbsp;&nbsp;severity: str; &nbsp;&nbsp;&nbsp;&nbsp;status: str<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;message: str; &nbsp;&nbsp;&nbsp;detail: Dict[str, Any]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;suggestion: Optional[str] = None<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@dataclass<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class EngineReport:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_name: str; &nbsp;&nbsp;total_rules: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;passed: int; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;warnings: int; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;not_applicable: int<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;results: List[RuleResult]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;@property<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def pass_rate(self) -&gt; float:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;checked = self.passed + self.failed<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.passed \/ checked if checked &gt; 0 else 0.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;@property<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def critical_fails(self) -&gt; List[RuleResult]:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return [r for r in self.results<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if r.status == &#8220;fail&#8221; and r.severity == &#8220;\u4e25\u91cd&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class ConditionEvaluator:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;OPERATORS = {<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;&gt;&#8221;: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lambda a,b: a &gt; b, &nbsp;&nbsp;&#8220;&gt;=&#8221;: lambda a,b: a &gt;= b,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;&lt;&#8220;: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lambda a,b: a &lt; b, &nbsp;&nbsp;&#8220;&lt;=&#8221;: lambda a,b: a &lt;= b,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;==&#8221;: &nbsp;&nbsp;&nbsp;&nbsp;lambda a,b: a == b, &nbsp;&#8220;!=&#8221;: lambda a,b: a != b,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;in&#8221;: &nbsp;&nbsp;&nbsp;&nbsp;lambda a,b: a in b, &nbsp;&#8220;not_in&#8221;: lambda a,b: a not in b,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def evaluate_conditions(self, conditions, params) -&gt; bool:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return all(<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.OPERATORS[c[&#8220;operator&#8221;]](<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self._get(params, c[&#8220;field&#8221;]), c[&#8220;value&#8221;])<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for c in conditions<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def _get(self, params, field):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v = params<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for k in field.split(&#8216;.&#8217;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if isinstance(v, dict): v = v.get(k)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elif isinstance(v, list):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try: v = v[int(k)]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;except: return None<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else: return None<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return v<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class RuleEngine:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def __init__(self, rules_dir=&#8221;rules&#8221;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.rules = []<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.evaluator = ConditionEvaluator()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for f in sorted(Path(rules_dir).glob(&#8220;**\/*.yaml&#8221;)):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pack = yaml.safe_load(open(f, encoding=&#8217;utf-8&#8242;))<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.rules.extend(pack.get(&#8216;rules&#8217;, []))<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def run(self, params) -&gt; EngineReport:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not isinstance(params, dict):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from dataclasses import asdict<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;params = asdict(params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results = [self._evaluate_rule(r, params) for r in self.rules]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return EngineReport(<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;project_name=params.get(&#8216;project_name&#8217;,&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total_rules=len(results),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;passed=sum(1 for r in results if r.status==&#8221;pass&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed=sum(1 for r in results if r.status==&#8221;fail&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;warnings=sum(1 for r in results if r.status==&#8221;warning&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;not_applicable=sum(1 for r in results if r.status==&#8221;not_applicable&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results=results<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def _evaluate_rule(self, rule, params) -&gt; RuleResult:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;apply_when = rule.get(&#8216;apply_when&#8217;, [])<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if apply_when and not self.evaluator.evaluate_conditions(apply_when, params):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return RuleResult(rule[&#8216;id&#8217;],rule[&#8216;name&#8217;],rule.get(&#8216;standard_ref&#8217;,&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;category&#8217;,&#8221;),rule.get(&#8216;severity&#8217;,&#8217;\u4e00\u822c&#8217;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;not_applicable&#8221;,&#8221;\u6761\u4ef6\u4e0d\u9002\u7528&#8221;,{})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;check = rule.get(&#8216;check&#8217;,{})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if check.get(&#8216;type&#8217;) == &#8216;foreach&#8217;:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self._eval_foreach(rule, check, params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self._eval_single(rule, check, params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def _eval_single(self, rule, check, params) -&gt; RuleResult:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;field &nbsp;= check[&#8216;field&#8217;]; &nbsp;&nbsp;op = check[&#8216;operator&#8217;]; &nbsp;&nbsp;threshold = check[&#8216;value&#8217;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;actual = self.evaluator._get(params, field)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;passed = self.evaluator.OPERATORS[op](actual, threshold) if actual is not None else False<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg = f&#8221;\u2713 {field} = {actual}&#8221; if passed else &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;fail_message&#8217;,&#8217;\u4e0d\u6ee1\u8db3\u8981\u6c42&#8217;).format(**{**params, &#8216;threshold&#8217;:threshold})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return RuleResult(rule[&#8216;id&#8217;],rule[&#8216;name&#8217;],rule.get(&#8216;standard_ref&#8217;,&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;category&#8217;,&#8221;),rule.get(&#8216;severity&#8217;,&#8217;\u4e00\u822c&#8217;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;pass&#8221; if passed else &#8220;fail&#8221;, msg,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;actual&#8221;:actual,&#8221;threshold&#8221;:threshold},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;suggestion&#8217;) if not passed else None)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def _eval_foreach(self, rule, check, params) -&gt; RuleResult:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items = params.get(check[&#8216;iterate&#8217;], [])<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed = []<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for item in items:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;merged = {**params, **item}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;actual = item.get(check.get(&#8216;check_field&#8217;))<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;op &nbsp;&nbsp;&nbsp;&nbsp;= check.get(&#8216;operator&#8217;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thresh = check.get(&#8216;check_value&#8217;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if actual is not None and not self.evaluator.OPERATORS[op](actual, thresh):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed.append({&#8216;item&#8217;:item,&#8217;actual&#8217;:actual,&#8217;threshold&#8217;:thresh})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not failed:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return RuleResult(rule[&#8216;id&#8217;],rule[&#8216;name&#8217;],rule.get(&#8216;standard_ref&#8217;,&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;category&#8217;,&#8221;),rule.get(&#8216;severity&#8217;,&#8217;\u4e00\u822c&#8217;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;pass&#8221;,f&#8221;\u2713 \u5168\u90e8 {len(items)} \u9879\u6ee1\u8db3\u8981\u6c42&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;checked&#8221;:len(items)})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msgs = [rule.get(&#8216;fail_message&#8217;,&#8221;).format(**{**params,**fi[&#8216;item&#8217;],<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8216;threshold&#8217;:fi[&#8216;threshold&#8217;]})<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for fi in failed]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return RuleResult(rule[&#8216;id&#8217;],rule[&#8216;name&#8217;],rule.get(&#8216;standard_ref&#8217;,&#8221;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;category&#8217;,&#8221;),rule.get(&#8216;severity&#8217;,&#8217;\u4e00\u822c&#8217;),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;fail&#8221;,&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;.join(msgs),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;failed_count&#8221;:len(failed),&#8221;items&#8221;:failed},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rule.get(&#8216;suggestion&#8217;))<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u4e94\u3001\u7b2c\u56db\u5c42\uff1aAI \u89e3\u91ca\u5c42<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5.1 &nbsp;\u8bbe\u8ba1\u539f\u5219<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AI \u89e3\u91ca\u5c42\u9075\u5faa\u300c\u4e0d\u53c2\u4e0e\u8ba1\u7b97\uff0c\u53ea\u53c2\u4e0e\u8868\u8fbe\u300d\u539f\u5219\u3002\u89c4\u5219\u5f15\u64ce\u8f93\u51fa\u7684\u662f\u673a\u5668\u683c\u5f0f\u7684\u5224\u65ad\u7ed3\u679c\uff08pass\/fail + \u6570\u503c\uff09\uff0cAI \u7684\u4f5c\u7528\u662f\u5c06\u8fd9\u4e9b\u7ed3\u679c\u8f6c\u6362\u4e3a\u7b26\u5408\u5efa\u7b51\u5de5\u7a0b\u884c\u4e1a\u4e60\u60ef\u7684\u4e13\u4e1a\u8bed\u8a00\uff0c\u5e76\u63d0\u4f9b\u6709\u5efa\u8bbe\u6027\u7684\u6574\u6539\u5efa\u8bae\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 AI \u6e29\u5ea6\uff08temperature\uff09\u8bbe\u7f6e\u4e3a 0.3\uff0c\u4fdd\u8bc1\u4e13\u4e1a\u6027\u548c\u4e00\u81f4\u6027<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u7cfb\u7edf\u63d0\u793a\u8bcd\u9501\u5b9a\u89d2\u8272\u4e3a\u300c\u8d44\u6df1\u5efa\u7b51\u89c4\u8303\u5ba1\u67e5\u5de5\u7a0b\u5e08\u300d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 AI \u4e0d\u5f97\u6539\u53d8\u89c4\u5219\u5f15\u64ce\u7684\u5224\u65ad\u7ed3\u8bba\uff0c\u53ea\u80fd\u8865\u5145\u89e3\u91ca<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u652f\u6301\u6d41\u5f0f\u8f93\u51fa\uff0c\u63d0\u5347\u5927\u62a5\u544a\u7684\u54cd\u5e94\u4f53\u9a8c<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5.2 &nbsp;AIExplainer \u7c7b\u5b9e\u73b0<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (AI \u89e3\u91ca\u5c42)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># services\/ai_explainer.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import httpx<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from typing import List<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class AIExplainer:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def __init__(self, api_url: str, api_key: str, model=&#8221;gpt-4&#8243;):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.api_url = api_url<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.api_key = api_key<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.model &nbsp;&nbsp;= model<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;SYSTEM_PROMPT = &#8220;&#8221;&#8221;\u4f60\u662f\u4e00\u4f4d\u8d44\u6df1\u5efa\u7b51\u89c4\u8303\u5ba1\u67e5\u5de5\u7a0b\u5e08\uff0c\u5177\u670920\u5e74\u4ee5\u4e0a\u5ba1\u67e5\u7ecf\u9a8c\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u5c06\u5efa\u7b51\u89c4\u8303\u81ea\u52a8\u6821\u9a8c\u7ed3\u679c\u8f6c\u5316\u4e3a\u4e13\u4e1a\u5ba1\u67e5\u62a5\u544a\uff0c\u8981\u6c42\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. \u8bed\u8a00\u4e13\u4e1a\uff0c\u7b26\u5408\u5efa\u7b51\u5de5\u7a0b\u884c\u4e1a\u8868\u8fbe\u4e60\u60ef<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2. \u5bf9\u4e0d\u5408\u89c4\u9879\uff1a\u660e\u786e\u5f15\u7528\u89c4\u8303\u6761\u6587\uff0c\u8bf4\u660e\u8fdd\u89c4\u5185\u5bb9\uff0c\u7ed9\u51fa\u53ef\u64cd\u4f5c\u7684\u6574\u6539\u5efa\u8bae<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3. \u6574\u6539\u5efa\u8bae\u6309\u4e25\u91cd\u7a0b\u5ea6\u6392\u5e8f<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4. \u6e29\u5ea6\u8bbe\u4e3a0.3\uff0c\u907f\u514d\u521b\u9020\u6027\u53d1\u6325\uff0c\u786e\u4fdd\u62a5\u544a\u53ef\u91cd\u590d&#8221;&#8221;&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;async def generate_report(self, engine_report, params: dict) -&gt; str:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed = [r for r in engine_report.results if r.status == &#8220;fail&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prompt = self._build_prompt(engine_report, params, failed)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return await self._call_llm(prompt)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def _build_prompt(self, report, params, failed_rules) -&gt; str:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;info = f&#8221;&#8221;&#8221;\u9879\u76ee\uff1a{params.get(&#8216;project_name&#8217;)}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u5efa\u7b51\u7c7b\u578b\uff1a{params.get(&#8216;building_type&#8217;)} | \u9ad8\u5ea6\uff1a{params.get(&#8216;building_height&#8217;)}m<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u603b\u9762\u79ef\uff1a{params.get(&#8216;total_area&#8217;)}\u33a1 | \u5c42\u6570\uff1a{params.get(&#8216;floor_count&#8217;)}F<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u6821\u9a8c\u6458\u8981\uff1a\u5171{report.total_rules}\u6761\u89c4\u5219\uff0c\u5408\u89c4{report.passed}\u6761\uff0c<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u4e0d\u5408\u89c4{report.failed}\u6761\uff0c\u5408\u89c4\u7387{report.pass_rate:.1%}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u4e0d\u5408\u89c4\u9879\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;&#8221;&#8221; + &#8220;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;.join(<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f&#8221;[{r.rule_id}] {r.rule_name}\uff08{r.standard_ref}\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u95ee\u9898\uff1a{r.message}&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for r in failed_rules<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return info + &#8220;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u8bf7\u8f93\u51fa\uff1a\u2460\u603b\u4f53\u8bc4\u4ef7 \u2461\u9010\u9879\u5206\u6790 \u2462\u6574\u6539\u4f18\u5148\u7ea7 \u2463\u603b\u7ed3&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;async def _call_llm(self, user_prompt: str) -&gt; str:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;async with httpx.AsyncClient(timeout=60.0) as client:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = await client.post(<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f&#8221;{self.api_url}\/v1\/chat\/completions&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;headers={&#8220;Authorization&#8221;: f&#8221;Bearer {self.api_key}&#8221;},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;json={<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;model&#8221;: self.model,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;temperature&#8221;: 0.3,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;max_tokens&#8221;: 2000,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;messages&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;role&#8221;: &#8220;system&#8221;, &#8220;content&#8221;: self.SYSTEM_PROMPT},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;role&#8221;: &#8220;user&#8221;, &nbsp;&nbsp;&#8220;content&#8221;: user_prompt},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return resp.json()[&#8220;choices&#8221;][0][&#8220;message&#8221;][&#8220;content&#8221;]<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u516d\u3001\u6570\u636e\u5e93\u8bbe\u8ba1<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6.1 &nbsp;\u6838\u5fc3\u8868\u7ed3\u6784\uff08PostgreSQL\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 SQL<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; schema.sql<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; \u9879\u76ee\u8868<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE TABLE projects (<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID PRIMARY KEY DEFAULT gen_random_uuid(),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;name &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VARCHAR(200) NOT NULL,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building_type VARCHAR(50),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;region &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VARCHAR(50) &nbsp;DEFAULT &#8216;national&#8217;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;created_at &nbsp;TIMESTAMP &nbsp;&nbsp;&nbsp;DEFAULT NOW()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; \u4e0a\u4f20\u6587\u4ef6\u8868<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE TABLE uploaded_files (<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID PRIMARY KEY DEFAULT gen_random_uuid(),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_id &nbsp;UUID REFERENCES projects(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;filename &nbsp;&nbsp;&nbsp;VARCHAR(300),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;file_path &nbsp;&nbsp;TEXT,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;status &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VARCHAR(20) &nbsp;DEFAULT &#8216;pending&#8217;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;error_msg &nbsp;&nbsp;TEXT,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;uploaded_at TIMESTAMP &nbsp;&nbsp;&nbsp;DEFAULT NOW()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; \u5efa\u7b51\u53c2\u6570\u5feb\u7167\u8868<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE TABLE building_params (<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID PRIMARY KEY DEFAULT gen_random_uuid(),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_id &nbsp;UUID REFERENCES projects(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;file_id &nbsp;&nbsp;&nbsp;&nbsp;UUID REFERENCES uploaded_files(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;params_json JSONB &nbsp;NOT NULL,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;building_height &nbsp;&nbsp;DECIMAL(8,2),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;total_area &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DECIMAL(12,2),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;floor_count &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INTEGER,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;extracted_at &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TIMESTAMP DEFAULT NOW()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE INDEX idx_params_project ON building_params(project_id);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; \u6821\u9a8c\u7ed3\u679c\u660e\u7ec6\u8868<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE TABLE check_results (<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID PRIMARY KEY DEFAULT gen_random_uuid(),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_id UUID REFERENCES projects(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;params_id &nbsp;UUID REFERENCES building_params(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;rule_id &nbsp;&nbsp;&nbsp;VARCHAR(50) &nbsp;NOT NULL,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;rule_name &nbsp;VARCHAR(200),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;category &nbsp;&nbsp;VARCHAR(50),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;severity &nbsp;&nbsp;VARCHAR(20),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;status &nbsp;&nbsp;&nbsp;&nbsp;VARCHAR(20), &nbsp;&nbsp;&#8212; pass\/fail\/warning\/not_applicable<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;message &nbsp;&nbsp;&nbsp;TEXT,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;detail &nbsp;&nbsp;&nbsp;&nbsp;JSONB,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;suggestion TEXT,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;checked_at TIMESTAMP DEFAULT NOW()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE INDEX idx_results_project ON check_results(project_id);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE INDEX idx_results_status &nbsp;ON check_results(status);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE INDEX idx_results_rule &nbsp;&nbsp;&nbsp;ON check_results(rule_id);<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212; AI \u751f\u6210\u62a5\u544a\u8868<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CREATE TABLE ai_reports (<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID PRIMARY KEY DEFAULT gen_random_uuid(),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;project_id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UUID REFERENCES projects(id),<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;report_text &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TEXT,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;prompt_tokens &nbsp;&nbsp;&nbsp;INTEGER,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;completion_tokens INTEGER,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;generated_at &nbsp;&nbsp;&nbsp;&nbsp;TIMESTAMP DEFAULT NOW()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">);<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u4e03\u3001\u6d4b\u8bd5\u7b56\u7565<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7.1 &nbsp;\u89c4\u5219\u5f15\u64ce\u5355\u5143\u6d4b\u8bd5<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6bcf\u6761\u89c4\u5219\u90fd\u9700\u8981\u6709\u8986\u76d6\u300c\u5e94\u901a\u8fc7\u300d\u300c\u5e94\u5931\u8d25\u300d\u300c\u4e0d\u9002\u7528\u300d\u4e09\u79cd\u573a\u666f\u7684\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5f62\u6210\u89c4\u5219\u6d4b\u8bd5\u77e9\u9635\u3002\u4ee5\u4e0b\u662f pytest \u6d4b\u8bd5\u793a\u4f8b\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (pytest)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># tests\/test_rule_engine.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import pytest<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from services.rule_engine import RuleEngine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@pytest.fixture<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def engine():<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return RuleEngine(rules_dir=&#8221;rules&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">@pytest.fixture<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def high_rise_office():<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;return {<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;project_name&#8221;: &nbsp;&nbsp;&#8220;\u6d4b\u8bd5\u9ad8\u5c42\u529e\u516c\u697c&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;building_type&#8221;: &nbsp;&#8220;\u529e\u516c\u5efa\u7b51&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;building_height&#8221;: 54.0,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;floor_count&#8221;: &nbsp;&nbsp;&nbsp;15,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;total_area&#8221;: &nbsp;&nbsp;&nbsp;&nbsp;18000,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;fire_zones&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;zone_id&#8221;:&#8221;FZ01&#8243;,&#8221;area&#8221;:980, &#8220;floor&#8221;:1,&#8221;has_sprinkler&#8221;:True},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;zone_id&#8221;:&#8221;FZ02&#8243;,&#8221;area&#8221;:1050,&#8221;floor&#8221;:2,&#8221;has_sprinkler&#8221;:True},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;zone_id&#8221;:&#8221;FZ03&#8243;,&#8221;area&#8221;:2600,&#8221;floor&#8221;:3,&#8221;has_sprinkler&#8221;:False}, # \u8d85\u6807<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;stairs&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;stair_id&#8221;:&#8221;ST01&#8243;,&#8221;net_width&#8221;:1.3,&#8221;stair_type&#8221;:&#8221;\u9632\u70df\u697c\u68af\u95f4&#8221;,&#8221;bottom_floor&#8221;:1,&#8221;top_floor&#8221;:15},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;stair_id&#8221;:&#8221;ST02&#8243;,&#8221;net_width&#8221;:1.0,&#8221;stair_type&#8221;:&#8221;\u9632\u70df\u697c\u68af\u95f4&#8221;,&#8221;bottom_floor&#8221;:1,&#8221;top_floor&#8221;:15}, # \u4e0d\u8db3<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;stair_count&#8221;:2, &#8220;exit_door_count&#8221;:2,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;max_evacuation_distance&#8221;:28, &#8220;has_refuge_floor&#8221;:False,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;has_elevator&#8221;:True, &#8220;min_sunshine_hours&#8221;:None,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;basement_count&#8221;:2, &#8220;footprint_area&#8221;:1200<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">class TestFireSafetyRules:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_exit_count_pass(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(high_rise_office)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r = next(x for x in report.results if x.rule_id == &#8220;FIRE_001&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert r.status == &#8220;pass&#8221;, r.message<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_stair_width_fail(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(high_rise_office)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r = next((x for x in report.results if x.rule_id == &#8220;FIRE_003&#8221;), None)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if r: assert r.status == &#8220;fail&#8221; and &#8220;ST02&#8221; in r.message<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_fire_zone_area_fail(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(high_rise_office)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r = next((x for x in report.results if x.rule_id == &#8220;FIRE_002&#8221;), None)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if r: assert r.status == &#8220;fail&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_no_refuge_not_required_at_54m(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(high_rise_office)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r = next((x for x in report.results if x.rule_id == &#8220;FIRE_005&#8221;), None)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if r: assert r.status == &#8220;not_applicable&#8221; &nbsp;# 54m &lt; 100m\uff0c\u4e0d\u9002\u7528<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_refuge_required_at_110m(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;params = {**high_rise_office, &#8220;building_height&#8221;:110, &#8220;has_refuge_floor&#8221;:False}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r = next((x for x in report.results if x.rule_id == &#8220;FIRE_005&#8221;), None)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if r: assert r.status == &#8220;fail&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_pass_rate_correct(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(high_rise_office)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;checked = report.passed + report.failed<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if checked &gt; 0:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert abs(report.pass_rate &#8211; report.passed\/checked) &lt; 0.001<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;def test_empty_stairs_returns_warning(self, engine, high_rise_office):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;params = {**high_rise_office, &#8220;stairs&#8221;:[], &#8220;stair_count&#8221;:0}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# \u4e0d\u5e94\u629b\u5f02\u5e38\uff0c\u8fd4\u56de warning \u6216 not_applicable<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert report is not None<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7.2 &nbsp;\u96c6\u6210\u6d4b\u8bd5\uff1a\u5168\u6d41\u7a0b\u9a8c\u8bc1<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 Python (\u96c6\u6210\u6d4b\u8bd5)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># tests\/test_integration.py<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">import asyncio, json, os<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from services.parser import IFCParser<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">from services.rule_engine import RuleEngine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">def test_full_pipeline_with_sample_ifc():<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;sample = &#8220;tests\/fixtures\/sample_high_rise.ifc&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;if not os.path.exists(sample):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pytest.skip(&#8220;\u6d4b\u8bd5 IFC \u6587\u4ef6\u4e0d\u5b58\u5728&#8221;)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;parser = IFCParser(sample)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;params = parser.extract_all()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u9a8c\u8bc1\u53c2\u6570\u5b8c\u6574\u6027<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;assert params.building_height &gt; 0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;assert params.floor_count &gt; 0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;assert len(params.fire_zones) &gt; 0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;engine = RuleEngine()<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;report = engine.run(params)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;# \u9a8c\u8bc1\u62a5\u544a\u5b8c\u6574\u6027<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;assert report.total_rules &gt; 0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;assert report.passed + report.failed + report.warnings + report.not_applicable == report.total_rules<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;print(f&#8221;\\n\u5408\u89c4\u7387\uff1a{report.pass_rate:.1%}\uff0c\u4e25\u91cd\u4e0d\u5408\u89c4\uff1a{len(report.critical_fails)}\u9879&#8221;)<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u516b\u3001\u90e8\u7f72\u914d\u7f6e<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8.1 &nbsp;Docker Compose \u751f\u4ea7\u914d\u7f6e<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 YAML (Docker Compose)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># docker-compose.yml<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">version: &#8216;3.9&#8217;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">services:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;api:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;build: .<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ports: [&#8220;8000:8000&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;environment:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATABASE_URL: postgresql:\/\/postgres:password@db:5432\/building_check<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIS_URL: &nbsp;&nbsp;&nbsp;redis:\/\/redis:6379\/0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LLM_API_KEY: &nbsp;${LLM_API_KEY}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RULES_DIR: &nbsp;&nbsp;&nbsp;\/app\/rules<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;volumes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; .\/rules:\/app\/rules:ro<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; uploads:\/app\/uploads<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;depends_on:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db: &nbsp;&nbsp;&nbsp;{ condition: service_healthy }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redis: { condition: service_healthy }<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;worker:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;build: .<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;command: celery -A worker.celery_app worker &#8211;loglevel=info -c 4<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;environment:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATABASE_URL: postgresql:\/\/postgres:password@db:5432\/building_check<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REDIS_URL: &nbsp;&nbsp;&nbsp;redis:\/\/redis:6379\/0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;depends_on: [db, redis]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;volumes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; .\/rules:\/app\/rules:ro<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; uploads:\/app\/uploads<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;db:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;image: postgres:15-alpine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;environment:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POSTGRES_DB: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;building_check<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POSTGRES_USER: &nbsp;&nbsp;&nbsp;&nbsp;postgres<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POSTGRES_PASSWORD: password<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;volumes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; postgres_data:\/var\/lib\/postgresql\/data<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; .\/schema.sql:\/docker-entrypoint-initdb.d\/schema.sql<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;healthcheck:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test: [&#8220;CMD-SHELL&#8221;,&#8221;pg_isready -U postgres&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;interval: 10s; retries: 5<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;redis:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;image: redis:7-alpine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;healthcheck:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test: [&#8220;CMD&#8221;,&#8221;redis-cli&#8221;,&#8221;ping&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;volumes: [redis_data:\/data]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;nginx:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;image: nginx:alpine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;ports: [&#8220;80:80&#8243;,&#8221;443:443&#8221;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;volumes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; .\/nginx.conf:\/etc\/nginx\/conf.d\/default.conf:ro<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8211; .\/frontend\/dist:\/usr\/share\/nginx\/html:ro<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;depends_on: [api]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">volumes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;postgres_data:; redis_data:; uploads:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8.2 &nbsp;\u6838\u5fc3\u4f9d\u8d56\u6e05\u5355<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 requirements.txt<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># requirements.txt<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">fastapi==0.104.1<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">uvicorn[standard]==0.24.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ifcopenshell==0.7.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">pyyaml==6.0.1<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">pydantic==2.5.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">sqlalchemy==2.0.23<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">asyncpg==0.29.0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">redis==5.0.1<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">celery==5.3.4<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">httpx==0.25.2<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">aiofiles==23.2.1<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python-multipart==0.0.6<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">pytest==7.4.3<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">pytest-asyncio==0.21.1<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u4e5d\u3001API \u63a5\u53e3\u8bbe\u8ba1<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9.1 &nbsp;RESTful \u63a5\u53e3\u4e00\u89c8<\/strong><strong><\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>\u65b9\u6cd5<\/strong><\/td><td><strong>\u8def\u5f84<\/strong><\/td><td><strong>\u8bf4\u660e<\/strong><\/td><\/tr><tr><td>POST<\/td><td>\/api\/v1\/projects\/{id}\/upload<\/td><td>\u4e0a\u4f20 IFC \u6587\u4ef6\uff0c\u89e6\u53d1\u540e\u53f0\u89e3\u6790<\/td><\/tr><tr><td>GET<\/td><td>\/api\/v1\/check\/status\/{file_id}<\/td><td>\u67e5\u8be2\u6587\u4ef6\u5904\u7406\u8fdb\u5ea6<\/td><\/tr><tr><td>POST<\/td><td>\/api\/v1\/check\/manual<\/td><td>\u624b\u52a8\u53c2\u6570\u76f4\u63a5\u89e6\u53d1\u6821\u9a8c<\/td><\/tr><tr><td>GET<\/td><td>\/api\/v1\/projects\/{id}\/results<\/td><td>\u83b7\u53d6\u9879\u76ee\u6821\u9a8c\u7ed3\u679c\u5217\u8868<\/td><\/tr><tr><td>GET<\/td><td>\/api\/v1\/projects\/{id}\/report<\/td><td>\u83b7\u53d6 AI \u751f\u6210\u7684\u5b8c\u6574\u62a5\u544a<\/td><\/tr><tr><td>GET<\/td><td>\/api\/v1\/rules<\/td><td>\u83b7\u53d6\u5f53\u524d\u52a0\u8f7d\u7684\u6240\u6709\u89c4\u5219\u5143\u6570\u636e<\/td><\/tr><tr><td>POST<\/td><td>\/api\/v1\/rules\/validate<\/td><td>\u6821\u9a8c\u89c4\u5219 YAML \u8bed\u6cd5\u662f\u5426\u5408\u6cd5<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9.2 &nbsp;\u6821\u9a8c\u63a5\u53e3\u8bf7\u6c42\/\u54cd\u5e94\u793a\u4f8b<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u25b8 JSON (API \u793a\u4f8b)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># \u8bf7\u6c42<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">POST \/api\/v1\/check\/manual<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Content-Type: application\/json<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">{<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8220;params&#8221;: {<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;project_name&#8221;: &nbsp;&#8220;\u793a\u4f8b\u529e\u516c\u697c&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;building_type&#8221;: &#8220;\u529e\u516c\u5efa\u7b51&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;building_height&#8221;: 54.0,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;floor_count&#8221;: &nbsp;&nbsp;15,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;total_area&#8221;: &nbsp;&nbsp;&nbsp;18000,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;fire_zones&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;zone_id&#8221;:&#8221;FZ01&#8243;,&#8221;area&#8221;:980,&#8221;floor&#8221;:1,&#8221;has_sprinkler&#8221;:true}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;],<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;stairs&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&#8220;stair_id&#8221;:&#8221;ST01&#8243;,&#8221;net_width&#8221;:1.3,&#8221;stair_type&#8221;:&#8221;\u9632\u70df\u697c\u68af\u95f4&#8221;,&#8221;bottom_floor&#8221;:1,&#8221;top_floor&#8221;:15}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;],<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;stair_count&#8221;:2, &#8220;exit_door_count&#8221;:2,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;max_evacuation_distance&#8221;:28, &#8220;has_refuge_floor&#8221;:false,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;has_elevator&#8221;:true, &#8220;min_sunshine_hours&#8221;:null,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;basement_count&#8221;:1, &#8220;footprint_area&#8221;:1200<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8220;region&#8221;: &#8220;national&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># \u54cd\u5e94<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">{<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8220;project_name&#8221;: &#8220;\u793a\u4f8b\u529e\u516c\u697c&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8220;summary&#8221;: {<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;total&#8221;: 12, &#8220;passed&#8221;: 9, &#8220;failed&#8221;: 2, &#8220;warnings&#8221;: 1,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&#8220;pass_rate&#8221;: &#8220;81.8%&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;},<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&#8220;critical_issues&#8221;: [<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;{<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;rule_id&#8221;: &#8220;FIRE_002&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;name&#8221;: &#8220;\u9632\u706b\u5206\u533a\u9762\u79ef\uff08\u9ad8\u5c42\u516c\u5171\u5efa\u7b51\uff09&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;message&#8221;: &#8220;\u9632\u706b\u5206\u533aFZ03\u9762\u79ef2600\u33a1\u8d85\u8fc7\u5141\u8bb8\u503c1000\u33a1&#8221;,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8220;suggestion&#8221;: &#8220;\u5c06\u9632\u706b\u5206\u533aFZ03\u62c6\u5206\u4e3a\u4e24\u4e2a\u72ec\u7acb\u9632\u706b\u5206\u533a\uff0c\u6216\u589e\u8bbe\u81ea\u52a8\u55b7\u6c34\u706d\u706b\u7cfb\u7edf\uff08\u9762\u79ef\u53ef\u6269\u81f32000\u33a1\uff09&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;&nbsp;]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">}<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u5341\u3001\u6269\u5c55\u8def\u7ebf\u56fe<\/strong><strong><\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>\u7b2c\u4e00\u9636\u6bb5 MVP\uff081-2\u4e2a\u6708\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6838\u5fc3\u76ee\u6807\uff1a10\u6761\u9ad8\u9891\u89c4\u5219\u8dd1\u901a\u5168\u6d41\u7a0b\u3002\u9a8c\u6536\u6807\u51c6\uff1a\u4e0a\u4f20 IFC \u2192 5\u5206\u949f\u5185\u8f93\u51fa\u62a5\u544a\uff0c\u8986\u76d6\u9632\u706b\u5206\u533a\u3001\u5b89\u5168\u51fa\u53e3\u3001\u697c\u68af\u5bbd\u5ea6\u4e09\u5927\u7c7b\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5b9e\u73b0 IFC \u4e0a\u4f20 + \u53c2\u6570\u62bd\u53d6\u6d41\u6c34\u7ebf<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5b8c\u6210 10 \u6761\u6838\u5fc3\u89c4\u5219\u7684 YAML \u7f16\u5199\u4e0e\u6d4b\u8bd5<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 FastAPI \u540e\u7aef + \u7b80\u5355 React \u524d\u7aef\u5c55\u793a\u7ed3\u679c<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5bf9\u63a5\u4e00\u4e2a LLM API \u751f\u6210\u57fa\u7840\u6587\u5b57\u62a5\u544a<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>\u7b2c\u4e8c\u9636\u6bb5 \u89c4\u6a21\u5316\uff083-6\u4e2a\u6708\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u6269\u5c55\u81f3 50-100 \u6761\u89c4\u5219\uff0c\u8986\u76d6\u4f4f\u5b85\/\u529e\u516c\/\u5546\u4e1a\u4e09\u7c7b\u5efa\u7b51<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u63a5\u5165\u5730\u65b9\u89c4\u8303\u5dee\u5f02\u5305\uff08\u5317\u4eac\/\u4e0a\u6d77\/\u5e7f\u4e1c\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u89c4\u5219\u53ef\u89c6\u5316\u7f16\u8f91\u5668\uff08\u8ba9\u89c4\u8303\u4e13\u5bb6\u65e0\u9700\u7f16\u7a0b\u5373\u53ef\u7ef4\u62a4\u89c4\u5219\uff09<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 Celery \u5f02\u6b65\u4efb\u52a1\u961f\u5217\uff0c\u652f\u6301\u5927\u6587\u4ef6\uff08&gt;200MB\uff09\u5e76\u53d1\u5904\u7406<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u6821\u9a8c\u62a5\u544a\u5bfc\u51fa\u4e3a PDF \/ Word \u683c\u5f0f<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>\u7b2c\u4e09\u9636\u6bb5 \u4ea7\u54c1\u5316\uff086-12\u4e2a\u6708\uff09<\/strong><strong><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 Revit \u63d2\u4ef6\uff1a\u8bbe\u8ba1\u8fc7\u7a0b\u4e2d\u5b9e\u65f6\u6821\u9a8c\uff0c\u5373\u65f6\u63d0\u793a\u4e0d\u5408\u89c4\u9879<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u89c4\u8303\u77e5\u8bc6\u56fe\u8c31\uff1a\u6761\u6587\u4e4b\u95f4\u7684\u5f15\u7528\u5173\u7cfb\u81ea\u52a8\u89e3\u6790<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u591a\u79df\u6237 SaaS\uff1a\u6309\u9879\u76ee\u6570\u91cf\u6216\u89c4\u5219\u5305\u8ba2\u9605\u8ba1\u8d39<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u4e0e BIM 360\/Bentium iTwin \u7b49\u5e73\u53f0\u6df1\u5ea6\u96c6\u6210<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 \u5386\u53f2\u5bf9\u6bd4\uff1a\u540c\u4e00\u9879\u76ee\u591a\u7248\u672c\u6821\u9a8c\u7ed3\u679c\u8d8b\u52bf\u5206\u6790<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>\u7ed3\u8bed<\/strong><strong><\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">\u8fd9\u5957\u7cfb\u7edf\u7684\u6838\u5fc3\u4ef7\u503c\u5728\u4e8e\u628a\u89c4\u8303\u7684\u4e13\u4e1a\u5224\u65ad\u529b\u4ece\u4eba\u8111\u4e2d\u89e3\u653e\u51fa\u6765\uff0c\u53d8\u6210\u53ef\u590d\u7528\u3001\u53ef\u7248\u672c\u5316\u3001\u53ef\u5ba1\u8ba1\u7684\u6570\u5b57\u8d44\u4ea7\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u89c4\u5219\u5f15\u64ce\u4fdd\u8bc1\u5224\u65ad\u7684\u786e\u5b9a\u6027\u4e0e\u53ef\u8ffd\u6eaf\u6027\uff1bAI \u5c42\u4fdd\u8bc1\u8868\u8fbe\u7684\u4e13\u4e1a\u6027\u4e0e\u53ef\u8bfb\u6027\uff1b\u5de5\u4f5c\u6d41\u4fdd\u8bc1\u6574\u4e2a\u8fc7\u7a0b\u7684\u81ea\u52a8\u5316\u4e0e\u89c4\u6a21\u5316\u3002\u4e09\u8005\u5206\u5de5\u660e\u786e\uff0c\u4e92\u4e0d\u8d8a\u4f4d\u2014\u2014\u8fd9\u662f\u6784\u5efa\u8fd9\u7c7b\u7cfb\u7edf\u6700\u91cd\u8981\u7684\u67b6\u6784\u539f\u5219\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u4ece\u4e00\u4e2a\u53ea\u8986\u76d6 10 \u6761\u89c4\u5219\u7684 MVP \u51fa\u53d1\uff0c\u5230\u6700\u7ec8\u6784\u5efa\u8986\u76d6\u6570\u767e\u6761\u89c4\u8303\u6761\u6587\u7684\u4e13\u4e1a\u5e73\u53f0\uff0c\u6bcf\u4e00\u6b65\u90fd\u8981\u4fdd\u6301\u89c4\u5219\u5f15\u64ce\u7684\u6838\u5fc3\u5730\u4f4d\u4e0d\u52a8\u6447\u3002AI \u662f\u9526\u4e0a\u6dfb\u82b1\uff0c\u89c4\u5219\u662f\u7cfb\u7edf\u7684\u7075\u9b42\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4e00\u3001\u7cfb\u7edf\u603b\u4f53\u8bbe\u8ba1 1.1 &nbsp;\u8bbe\u8ba1\u54f2\u5b66 \u5efa\u7b51\u89c4\u8303\u6821\u9a8c\u7cfb\u7edf\u7684\u672c\u8d28\u662f\u6761\u4ef6\u5224\u65ad\u5f15\u64ce\uff0c\u800c\u975e\u751f\u6210\u5f0f AI \u5e94\u7528\u3002 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[6,14],"tags":[451],"class_list":["post-7847","post","type-post","status-publish","format-standard","hentry","category-build","category-teacher","tag-llm","entry"],"_links":{"self":[{"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/posts\/7847","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/comments?post=7847"}],"version-history":[{"count":0,"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/posts\/7847\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/media?parent=7847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/categories?post=7847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xinyixx.com\/index.php\/wp-json\/wp\/v2\/tags?post=7847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}