初始提交:MCP Server Demo(ERP/CRM/Bexell 三模组,动态 Bearer Token 鉴权)

This commit is contained in:
2026-09-02 17:08:10 +08:00
commit 71c3d86ccf
28 changed files with 3983 additions and 0 deletions
+278
View File
@@ -0,0 +1,278 @@
"""
MCP for Bexell — 试产排程评估及优化系统
使用 MCPServer 框架,4 个工具:设备产能查询、排产占用查询、品号负荷查询、试产产能评估
HTTP 模式,挂载在 /bexell/mcp 路径
"""
import json
from datetime import datetime
from typing import Dict, List
from sqlalchemy import func, text
from mcp.server import MCPServer
from auth import get_auth
from .db import get_db
from .models import EquipmentCapacity, ProductEquipmentLoad, ProductEquipmentSchedule
# 鉴权:独立 Bearer Token,API Key 来自 BEXELL_MCP_AUTH_API_KEY 环境变量
_auth, _token_verifier = get_auth(service="BEXELL")
bexell_server = MCPServer(
name="bexell-scheduling",
description="试产排程评估及优化系统",
version="1.0.0",
auth=_auth,
token_verifier=_token_verifier,
)
# ============================================================
# 工具 1: get_equipment_by_type — 按设备类型查询设备产能信息
# ============================================================
@bexell_server.tool()
async def get_equipment_by_type(equipment_type: str) -> List[Dict]:
"""
根据设备类型获取该类型所有设备的基础产能信息。
Args:
equipment_type: 设备类型,如"冲压"、"表面"、"检测"
Returns:
设备列表,每个设备包含:
- equipment_code: 设备编号
- equipment_name: 设备名称
- equipment_type: 设备类型
- max_capacity: 极限产能(H)
- standard_capacity: 标准产能(H)
- min_startup: 最小开机产能(H)
- line_change_cost: 换线成本系数
"""
db = next(get_db())
try:
rows = db.query(EquipmentCapacity).filter(
EquipmentCapacity.equipment_type == equipment_type
).order_by(EquipmentCapacity.equipment_code).all()
return [{
"equipment_code": r.equipment_code,
"equipment_name": r.equipment_name,
"equipment_type": r.equipment_type,
"max_capacity": float(r.max_capacity),
"standard_capacity": float(r.standard_capacity),
"min_startup": float(r.min_startup),
"line_change_cost": float(r.line_change_cost),
} for r in rows]
finally:
db.close()
# ============================================================
# 工具 2: get_schedule_occupied — 查询设备排产占用产能合计
# ============================================================
@bexell_server.tool()
async def get_schedule_occupied(equipment_type: str) -> List[Dict]:
"""
获取指定类型全部设备的已占用产能汇总。
Args:
equipment_type: 设备类型,如"冲压"、"表面"、"检测"
Returns:
设备占用列表,每个设备包含:
- equipment_code: 设备编号
- equipment_name: 设备名称
- total_occupied_capacity: 占用产能合计(H)
"""
db = next(get_db())
try:
rows = db.query(
ProductEquipmentSchedule.equipment_code,
ProductEquipmentSchedule.equipment_name,
func.coalesce(
func.sum(ProductEquipmentSchedule.occupied_capacity), 0
).label("total_occupied")
).filter(
ProductEquipmentSchedule.equipment_type == equipment_type
).group_by(
ProductEquipmentSchedule.equipment_code,
ProductEquipmentSchedule.equipment_name
).order_by(
ProductEquipmentSchedule.equipment_code
).all()
return [{
"equipment_code": r.equipment_code,
"equipment_name": r.equipment_name,
"total_occupied_capacity": float(r.total_occupied),
} for r in rows]
finally:
db.close()
# ============================================================
# 工具 3: get_product_load — 查询品号在各设备的标准产能
# ============================================================
@bexell_server.tool()
async def get_product_load(product_code: str, equipment_type: str) -> List[Dict]:
"""
获取指定品号在目标设备类型各设备上的标准产能。
Args:
product_code: 品号,如"A0003"
equipment_type: 设备类型,如"冲压"、"表面"、"检测"
Returns:
品号设备负荷列表,每条记录包含:
- product_code: 品号
- product_name: 品名
- equipment_code: 设备编号
- equipment_name: 设备名称
- standard_capacity: 该品号在该设备上的标准产能
"""
db = next(get_db())
try:
rows = db.query(ProductEquipmentLoad).join(
EquipmentCapacity,
ProductEquipmentLoad.equipment_code == EquipmentCapacity.equipment_code
).filter(
ProductEquipmentLoad.product_code == product_code,
EquipmentCapacity.equipment_type == equipment_type
).order_by(
ProductEquipmentLoad.equipment_code
).all()
return [{
"product_code": r.product_code,
"product_name": r.product_name,
"equipment_code": r.equipment_code,
"equipment_name": r.equipment_name,
"standard_capacity": float(r.standard_capacity),
} for r in rows]
finally:
db.close()
# ============================================================
# 工具 4: evaluate_trial_capacity — 一体化试产产能评估
# ============================================================
@bexell_server.tool()
async def evaluate_trial_capacity(
product_code: str,
trial_quantity: int
) -> Dict:
"""
一体化试产产能评估:计算指定品号在指定设备类型下所有设备的可用产能,
并推荐产能最大值设备。
计算公式:
可用产能 = 极限产能 − 占用产能合计 − 换线成本 − 最小开机×0.1 − 试产数量×品号标准产能
Args:
product_code: 品号,如"A0003"
trial_quantity: 试产数量,如 10
Returns:
评估结果字典,包含:
- product_code: 品号
- product_name: 品名
- trial_quantity: 试产数量
- evaluated_at: 评估时间
- results: 设备评估明细列表(按可用产能降序)
- recommended: 推荐设备(可用产能最大且>=0)
"""
db = next(get_db())
try:
# 查询品名
product = db.execute(text(
"SELECT product_name FROM product_info WHERE product_code = :code"
), {"code": product_code}).first()
if not product:
return {"error": f"品号 {product_code} 不存在"}
# 核心聚合 SQL
sql = text("""
SELECT
ec.equipment_code,
ec.equipment_name,
ec.equipment_type,
ec.max_capacity,
ec.line_change_cost,
ec.min_startup * 0.1 AS min_startup_buffer,
COALESCE(occ.total_occupied, 0) AS occupied_capacity,
pel.standard_capacity AS product_standard_capacity,
:trial_qty * pel.standard_capacity AS trial_demand,
(ec.max_capacity
- COALESCE(occ.total_occupied, 0)
- ec.line_change_cost
- ec.min_startup * 0.1
- :trial_qty * pel.standard_capacity
) AS available_capacity
FROM equipment_capacity ec
LEFT JOIN (
SELECT equipment_code, SUM(occupied_capacity) AS total_occupied
FROM product_equipment_schedule
GROUP BY equipment_code
) occ ON ec.equipment_code = occ.equipment_code
JOIN product_equipment_load pel
ON ec.equipment_code = pel.equipment_code
AND pel.product_code = :product_code
WHERE ec.equipment_type = (
SELECT equipment_type FROM equipment_capacity ec2
WHERE ec2.equipment_code = pel.equipment_code
LIMIT 1
)
ORDER BY available_capacity DESC
""")
rows = db.execute(sql, {
"product_code": product_code,
"trial_qty": trial_quantity
}).mappings().all()
# 组装结果
results = []
for idx, r in enumerate(rows, 1):
available = float(r.available_capacity)
results.append({
"rank": idx,
"equipment_code": r.equipment_code,
"equipment_name": r.equipment_name,
"equipment_type": r.equipment_type,
"max_capacity": float(r.max_capacity),
"occupied_capacity": float(r.occupied_capacity),
"line_change_cost": float(r.line_change_cost),
"min_startup_buffer": float(r.min_startup_buffer),
"product_standard_capacity": float(r.product_standard_capacity),
"trial_demand": float(r.trial_demand),
"available_capacity": available,
"status": "可用" if available >= 0 else "已满负荷",
})
# 推荐设备(第一个可用且 >= 0 的)
recommended = None
for r in results:
if r["available_capacity"] >= 0:
recommended = {
"equipment_code": r["equipment_code"],
"equipment_name": r["equipment_name"],
"available_capacity": r["available_capacity"],
}
break
return {
"product_code": product_code,
"product_name": product.product_name,
"trial_quantity": trial_quantity,
"evaluated_at": datetime.now().isoformat(),
"results": results,
"recommended": recommended,
}
finally:
db.close()