{
  "schema": "cad-aspen-public-api-guide/v1",
  "version": "1.4.0",
  "language": "zh-CN",
  "base_url": "https://api.aspenflowsim.top",
  "authentication": "公开读取接口不需要鉴权；仅 /api/v1/admin/ 下的维护接口需要同源会话。",
  "cors": "Access-Control-Allow-Origin: *",
  "discovery": {
    "human_documentation_url": "/cad-api/",
    "catalog_url": "/api/v1/cad-blocks",
    "openapi_url": "/api/v1/cad-openapi.json",
    "drawing_contract_url": "/api/v1/cad-contract.json",
    "occupancy_schema_url": "/api/v1/equipment-occupancy-schema.json"
  },
  "full_symbol_contract": {
    "endpoint": "/api/v1/cad-blocks/{template_id}",
    "required_fields": [
      "schema",
      "catalog",
      "bounds_local",
      "insertion_base_local",
      "reference_insertion_point",
      "entity_count",
      "entities",
      "layer_styles",
      "linetype_styles",
      "nested_block_dependencies",
      "connection_ports",
      "api_resolution"
    ],
    "equipment_only_required_field": "equipment_occupancy_frame",
    "field_meanings": {
      "catalog": "模板身份、设备类别、显示名称和 Aspen 候选映射元数据。",
      "bounds_local": "全部图元的局部包围框；可能包含文字、引线或辅助结构，不等同于设备占地框。",
      "insertion_base_local": "模板局部坐标中与世界 insertion 对齐的唯一插入基准。",
      "reference_insertion_point": "插入基点来源和网页标记元数据；其 local 应与 insertion_base_local 一致。",
      "entities": "可重建的 CAD 矢量实体；geometry 保留局部几何，样式由实体字段和 layer_styles/linetype_styles 联合解析。",
      "nested_block_dependencies": "嵌套块依赖；调用方必须递归加载并叠加父子变换。",
      "equipment_occupancy_frame": "设备主体相对插入基点的紧致占地矩形；仅 equipment_symbol 强制提供。",
      "connection_ports": "当前公开的工艺连接点、方向、状态和人工复核标志。",
      "connection_configuration": "母模板可选端口、数量约束、选择优先级及稳定 port_uid 到零个、一个或多个支管图元 handle 的绑定。",
      "instance_configuration": "本次响应是否应用裁剪、请求数量、实际启用及移除的端口；不改变母模板。",
      "api_resolution": "requested/resolved/fallback；未知模板 HTTP 仍返回 200，必须检查 fallback。"
    },
    "reconstruction_order": [
      "读取完整模板和 nested_block_dependencies。",
      "以 insertion_base_local 为局部插入基准，递归重建 entities。",
      "按 layer_styles、linetype_styles、实体颜色和 BYLAYER/BYBLOCK 规则恢复样式。",
      "调用 placement 获得变换后的设备世界占地框和端口。",
      "仅对 connectable=true 且无需复核的端口执行自动接线。"
    ]
  },
  "recommended_workflow": [
    "GET /api/v1/cad-blocks 读取模板目录；优先按 aspen_modules 或 equipment_template_mapping 选择 template_id。",
    "GET /api/v1/cad-blocks/{template_id} 读取完整矢量、图层、嵌套依赖、插入基点和当前公开端口。",
    "从 Aspen 拓扑统计目标设备的实际入口数和出口数；不要让 CAD 图块接口根据设备外形或名称猜测数量。",
    "若模板存在 connection_configuration，则在同一次实例请求中同时传 inlet_count 与 outlet_count；服务端按优先级和图元绑定返回匹配后的 entities、connection_ports 与重算后的 equipment_occupancy_frame。",
    "GET /api/v1/cad-blocks/{template_id}/placement 读取布图所需的占位矩形、面积、世界 AABB 和变换后的端口。",
    "仅当 ports.connectable=true 时接线；从 items[].world 起线，首段沿 items[].world_normal。",
    "用 occupancy.world_aabb 做初排和碰撞检测；旋转设备时必须使用接口返回结果或变换四角后重新求 AABB。",
    "递归读取 nested_block_dependencies，完成图元重建后再输出 DXF/DWG。"
  ],
  "endpoints": [
    {
      "method": "GET",
      "path": "/api/v1/cad-blocks",
      "purpose": "模板目录、Aspen 映射、端口摘要、矩形摘要和文档发现。"
    },
    {
      "method": "GET",
      "path": "/api/v1/cad-blocks/{template_id}",
      "purpose": "完整母模板；配置化设备同时提供 inlet_count/outlet_count 时返回按绑定裁剪后的可编辑实例 JSON。未知 ID 回退万能设备块并返回 api_resolution。"
    },
    {
      "method": "GET",
      "path": "/api/v1/cad-blocks/{template_id}/ports",
      "purpose": "完整母模板端口；配置化请求返回本次实际启用的 inN/outN。"
    },
    {
      "method": "GET",
      "path": "/api/v1/cad-blocks/{template_id}/occupancy",
      "purpose": "插入基点相对的紧致矩形、四角和局部面积。"
    },
    {
      "method": "GET",
      "path": "/api/v1/cad-blocks/{template_id}/placement",
      "purpose": "推荐调用：配置化设备先传实际进出口数量，再按插入位置、缩放和旋转返回匹配实例的世界占位框与世界端口。"
    }
  ],
  "placement_query": {
    "insertion_x": {
      "type": "number",
      "default": 0,
      "meaning": "图块插入基点的世界 X。"
    },
    "insertion_y": {
      "type": "number",
      "default": 0,
      "meaning": "图块插入基点的世界 Y。"
    },
    "insertion_z": {
      "type": "number",
      "default": 0,
      "meaning": "图块插入基点的世界 Z；PFD 通常为 0。"
    },
    "scale_x": {
      "type": "number",
      "default": 1,
      "constraint": "不能为 0。"
    },
    "scale_y": {
      "type": "number",
      "default": 1,
      "constraint": "不能为 0。"
    },
    "rotation_deg": {
      "type": "number",
      "default": 0,
      "meaning": "逆时针角度。"
    },
    "inlet_count": {
      "type": "integer",
      "required_with": "outlet_count",
      "source": "调用方从 Aspen 拓扑统计",
      "meaning": "目标实例实际入口数量；仅 connection_configuration 图块支持，服务端不推测。"
    },
    "outlet_count": {
      "type": "integer",
      "required_with": "inlet_count",
      "source": "调用方从 Aspen 拓扑统计",
      "meaning": "目标实例实际出口数量；必须与 inlet_count 同时提供，服务端不推测。"
    }
  },
  "instance_request_protocol": {
    "applies_when": "模板响应或目录摘要包含 connection_configuration。",
    "caller_responsibility": "先解析 Aspen 设备上下游流股，统计目标设备实际 inlet_count 与 outlet_count；两者必须在请求前确定。",
    "request": "在同一次 symbol、ports 或 placement GET 请求中同时传 inlet_count 与 outlet_count。无需先创建服务器会话，也不是让服务端分析 Aspen 拓扑。",
    "server_responsibility": "按 connection_configuration.bindings.priority 选取指定数量的端口，再按 geometry_binding.entity_handles 生成与这些端口匹配的图形实例。",
    "response": "只把响应中的 entities、connection_ports、equipment_occupancy_frame 和 instance_configuration 作为本次设备实例使用。",
    "fixed_template_rule": "模板没有 connection_configuration 时不得传数量参数；直接使用模板固定端口与固定图形。",
    "mother_template_rule": "不传数量只用于读取或维护完整母模板，不应作为已知实际进出口数量的最终 PFD 实例。"
  },
  "configurable_connections": {
    "quantity_source": "inlet_count/outlet_count 必须由调用方先从 Aspen 拓扑得出；服务端不从图形、名称或默认值推测实际数量。",
    "mother_template": "不传 inlet_count/outlet_count 时返回完整母模板、全部可选端口和全部支管实体，仅供发现、审阅和维护。",
    "instance": "同时传入实际数量时，服务端按 connection_configuration.bindings.priority 选择端口，并依据 geometry_binding.entity_handles 返回匹配图形。",
    "query_endpoints": [
      "/api/v1/cad-blocks/{template_id}",
      "/api/v1/cad-blocks/{template_id}/ports",
      "/api/v1/cad-blocks/{template_id}/placement"
    ],
    "pair_rule": "inlet_count 与 outlet_count 必须同时提供；缺一、越界或对非配置图块使用均返回 422。",
    "identity_rule": "bindings 使用稳定 port_uid；显示 ID 不作为持久绑定主键。",
    "geometry_rule": "geometry_binding.entity_handles 可为空、单个或多个；空关联只移除端口。共享图元只在所有引用它的端口都未启用时移除。",
    "recalculated_fields": [
      "entity_count",
      "bounds_local",
      "equipment_occupancy_frame",
      "placement.occupancy.world_aabb"
    ],
    "response_field": "instance_configuration",
    "mixer_support": {
      "template_id": "Mixer_01",
      "inlet_count": {
        "min": 2,
        "max": 5
      },
      "outlet_count": {
        "min": 1,
        "max": 1
      }
    }
  },
  "coordinate_contract": {
    "local_coordinate_system": "AutoCAD 图块局部笛卡尔坐标，X 向右、Y 向上。",
    "insertion_base_local": "局部坐标中用于对准新图插入点的基准。多数模板为 [0,0,0]，调用端不得假定。",
    "point_formula": "world = insertion + R(rotation_deg) * diag(scale_x, scale_y) * (local - insertion_base_local)",
    "normal_formula": "world_normal = normalize(R(rotation_deg) * diag(scale_x, scale_y) * normal)",
    "units": "数值沿用源 CAD drawing units。area 字段是 drawing_unit²，不应在未知 INSUNITS 时标成 m²。"
  },
  "occupancy": {
    "meaning": "设备实体的零留白轴对齐紧致包络，仅用于设备初排、间距和碰撞检测；不是管线净距框，也不包含位号、端口引线或工艺留白。",
    "limits_from_insertion_base": {
      "left": "从插入基点到设备最左几何极限的有符号 X 偏移。",
      "bottom": "从插入基点到设备最下几何极限的有符号 Y 偏移。",
      "right": "从插入基点到设备最右几何极限的有符号 X 偏移。",
      "top": "从插入基点到设备最上几何极限的有符号 Y 偏移。"
    },
    "size": {
      "width": "right-left",
      "height": "top-bottom"
    },
    "area_local_square_drawing_units": "width*height；这是局部矩形面积，不是设备真实投影面积。",
    "world_corners": "按给定插入、缩放、旋转变换后的四角，顺序为左下、右下、右上、左上。",
    "world_aabb": "由 world_corners 重新求得的世界轴对齐包围框；碰撞检测应使用该字段。",
    "collision_field": "placement.occupancy.world_aabb",
    "padding_rule": "自动排布应在紧致框外另加项目级水平/垂直净距，不得回写模板 JSON。"
  },
  "ports": {
    "availability": "connection_ports.connectable=true 且 ports 非空时才允许自动接线。",
    "id": "展示编号；同方向端口按 in1/in2 或 out1/out2 连续排序。",
    "port_uid": "若存在则是跨重排稳定身份；程序保存绑定时优先用它。",
    "flow": "in 为进入设备，out 为离开设备。",
    "local": "唯一保存的模板局部连接坐标；它不是已扣除插入基点的偏移量。",
    "derived_offset": "调用时计算 local - insertion_base_local；JSON 不重复保存 offset_from_insertion_base。",
    "normal": "局部单位方向；用于管线离开设备的第一段，不能当作第二个坐标。",
    "world": "placement 接口计算后的世界连接坐标。",
    "world_normal": "placement 接口计算后的世界首段方向。",
    "connection_point_field": "placement.ports.items[].world",
    "first_segment_direction_field": "placement.ports.items[].world_normal",
    "side": "局部语义侧；旋转后判断绘图方向应使用 world_normal。",
    "service": "process 等介质/用途分类。",
    "role": "process_inlet、process_outlet、vapor_outlet 等端口职责。",
    "status_rule": "needs_manual_definition、draft 或 review_required=true 时不得自动作为最终 CAD 接口。",
    "routing_rule": "从 source.world 沿 source.world_normal 引出短直段，从 target.world 沿 target.world_normal 反向预留进入段，中间再做正交寻路。"
  },
  "fallback": {
    "template_id": "GenericEquipmentBlock_01",
    "when": "请求的 template_id 不存在。",
    "detection": "检查 api_resolution.fallback 或 placement.fallback；不要只比较响应 HTTP 状态。",
    "geometry": "48 x 48 正方形，左侧 6 个输入、右侧 6 个输出。",
    "restriction": "回退块可保证流程可视化，但不能代表真实设备选型；最终出图前必须替换或确认。"
  },
  "examples": {
    "catalog": "GET https://api.aspenflowsim.top/api/v1/cad-blocks",
    "symbol": "GET https://api.aspenflowsim.top/api/v1/cad-blocks/Pump_01",
    "ports": "GET https://api.aspenflowsim.top/api/v1/cad-blocks/GenericEquipmentBlock_01/ports",
    "occupancy": "GET https://api.aspenflowsim.top/api/v1/cad-blocks/Pump_01/occupancy",
    "placement": "GET https://api.aspenflowsim.top/api/v1/cad-blocks/Pump_01/placement?insertion_x=200&insertion_y=100&scale_x=1&scale_y=1&rotation_deg=90",
    "configured_mixer": "GET https://api.aspenflowsim.top/api/v1/cad-blocks/Mixer_01/placement?inlet_count=2&outlet_count=1&insertion_x=200&insertion_y=100"
  }
}