317 lines
16 KiB
Python
317 lines
16 KiB
Python
# ============================================================
|
||
# 行业工艺路线结构化模板库(moduleId: knowledge-routing-templates, 可重生 ✅)
|
||
# machining_patterns 的文本知识 → 结构化模板(SQLite routing_templates 表):
|
||
# - RAG 回答「怎么加工」(文本),模板库供向导「直接生成路线」(结构化)
|
||
# - 知识文档与模板互相引用:template.asset_title ↔ 知识资产标题
|
||
# 面向整个机加工行业:车/铣/钻镗/磨/钣金/焊接/装配/城轨装配。
|
||
# ============================================================
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from sqlalchemy import select
|
||
|
||
from server.auth.context import get_identity
|
||
|
||
# 内置模板定义:steps = (seq, 工序码, 工序名, 设备类型, 工时下限, 上限, setup, changeover)
|
||
_BUILTIN: list[dict[str, Any]] = [
|
||
{
|
||
"code": "TPL-TURNING", "name": "车削类零件(轴/套/盘)", "category": "车削",
|
||
"keywords": "车削,轴,套,盘,销,外圆,螺纹,回转体",
|
||
"description": "回转体零件典型路线:下料→车端面→粗车→半精车→切槽/螺纹→精车→去毛刺→终检",
|
||
"asset_title": "车削工艺路线模式",
|
||
"steps": [
|
||
(10, "OP-BLANK", "下料", "锯床/剪板机", 2, 5, 10, 5),
|
||
(20, "OP-FACE", "车端面打中心孔", "卧式车床", 3, 8, 15, 10),
|
||
(30, "OP-ROUGH-TURN", "粗车外圆", "数控车床", 5, 15, 20, 15),
|
||
(40, "OP-SEMI-TURN", "半精车", "数控车床", 4, 10, 15, 10),
|
||
(50, "OP-GROOVE", "切槽/车螺纹", "数控车床", 3, 8, 15, 10),
|
||
(60, "OP-FINISH-TURN", "精车", "数控车床", 4, 12, 20, 15),
|
||
(70, "OP-DEBURR", "去毛刺", "钳工台", 2, 5, 5, 0),
|
||
(80, "OP-INSPECT", "终检", "检验台", 3, 6, 5, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-MILLING", "name": "铣削类零件(箱体/支架/平板)", "category": "铣削",
|
||
"keywords": "铣削,箱体,支架,平板,型腔,平面,壳体",
|
||
"description": "箱体支架类典型路线:毛坯→粗铣基准面→粗铣各面→半精→精铣→钻孔攻丝→去毛刺→终检",
|
||
"asset_title": "铣削工艺路线模式",
|
||
"steps": [
|
||
(10, "OP-BLANK", "下料/毛坯", "锯床", 2, 5, 10, 5),
|
||
(20, "OP-ROUGH-MILL-REF", "粗铣基准面", "立式加工中心", 8, 20, 25, 15),
|
||
(30, "OP-ROUGH-MILL", "粗铣各面/槽", "立式加工中心", 10, 30, 20, 15),
|
||
(40, "OP-SEMI-MILL", "半精铣", "立式加工中心", 8, 20, 15, 10),
|
||
(50, "OP-FINISH-MILL", "精铣", "立式加工中心", 8, 25, 20, 15),
|
||
(60, "OP-DRILL-TAP", "钻孔攻丝", "钻攻中心", 5, 15, 15, 10),
|
||
(70, "OP-DEBURR", "去毛刺", "钳工台", 3, 6, 5, 0),
|
||
(80, "OP-INSPECT", "终检", "三坐标/检验台", 5, 10, 5, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-HOLE", "name": "孔系加工(钻/镗/铰)", "category": "钻镗",
|
||
"keywords": "钻孔,镗孔,铰孔,攻丝,孔系,深孔",
|
||
"description": "孔系典型路线:钻中心孔→钻→扩→铰/镗→锪窝→攻丝;高精度孔粗镗→半精镗→精镗",
|
||
"asset_title": "钻镗孔系工艺模式",
|
||
"steps": [
|
||
(10, "OP-CENTER", "钻中心孔", "钻床", 1, 3, 10, 5),
|
||
(20, "OP-DRILL", "钻孔", "摇臂钻/加工中心", 2, 8, 10, 8),
|
||
(30, "OP-REAM-BORE", "铰孔/镗孔", "镗床/加工中心", 3, 10, 15, 10),
|
||
(40, "OP-TAP", "攻丝", "钻攻中心", 1, 4, 10, 5),
|
||
(50, "OP-INSPECT", "孔位检验", "检验台", 2, 5, 5, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-GRINDING", "name": "磨削精加工", "category": "磨削",
|
||
"keywords": "磨削,精磨,外圆磨,平面磨,淬火,超精",
|
||
"description": "热处理后精加工:校直→粗磨→半精磨→精磨→(超精/研磨)→检验;磨削常为瓶颈",
|
||
"asset_title": "磨削与超精工艺模式",
|
||
"steps": [
|
||
(10, "OP-STRAIGHTEN", "校直", "校直机", 2, 5, 10, 5),
|
||
(20, "OP-ROUGH-GRIND", "粗磨", "外圆磨床", 5, 12, 25, 20),
|
||
(30, "OP-SEMI-GRIND", "半精磨", "外圆磨床", 4, 10, 20, 15),
|
||
(40, "OP-FINISH-GRIND", "精磨", "外圆磨床", 5, 15, 25, 20),
|
||
(50, "OP-INSPECT", "形位检验", "圆度仪/检验台", 3, 8, 5, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-SHEETMETAL", "name": "钣金冲压", "category": "钣金",
|
||
"keywords": "钣金,冲压,折弯,冲裁,拉伸,板料",
|
||
"description": "板料典型路线:开料→冲裁→折弯→整形→去毛刺→表面处理",
|
||
"asset_title": "钣金冲压工艺模式",
|
||
"steps": [
|
||
(10, "OP-SHEAR", "开料", "剪板机", 1, 3, 10, 5),
|
||
(20, "OP-PUNCH", "冲裁/冲孔", "冲床", 0.5, 2, 20, 15),
|
||
(30, "OP-BEND", "折弯", "折弯机", 1, 4, 15, 10),
|
||
(40, "OP-REFORM", "整形", "整形模/压机", 1, 3, 10, 8),
|
||
(50, "OP-DEBURR", "去毛刺", "打磨台", 1, 3, 5, 0),
|
||
(60, "OP-SURFACE", "表面处理", "喷涂/电镀线", 3, 10, 15, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-WELDING", "name": "焊接结构件", "category": "焊接",
|
||
"keywords": "焊接,结构件,坡口,组对,探伤,焊缝",
|
||
"description": "下料→坡口→组对点固→焊接→矫正→探伤→机加接口→涂装;先焊后机加保证接口精度",
|
||
"asset_title": "焊接结构件工艺模式",
|
||
"steps": [
|
||
(10, "OP-CUT", "下料/坡口", "等离子切割/铣边机", 3, 8, 15, 10),
|
||
(20, "OP-FIT", "组对点固", "组对平台", 5, 15, 20, 10),
|
||
(30, "OP-WELD", "焊接", "焊接工位/机器人", 10, 40, 25, 20),
|
||
(40, "OP-CORRECT", "矫正", "火焰/机械矫正", 3, 10, 10, 5),
|
||
(50, "OP-NDT", "探伤", "UT/MT 探伤", 5, 15, 10, 0),
|
||
(60, "OP-MACHINE-IF", "机加接口", "加工中心", 8, 20, 25, 15),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-ASSEMBLY", "name": "机械装配(部装→总装)", "category": "装配",
|
||
"keywords": "装配,部装,总装,机电,调试,测试",
|
||
"description": "零部件齐套→部装→分总成→总装→电气接线→功能测试→终检→包装;部装常为瓶颈",
|
||
"asset_title": "机械装配工艺模式",
|
||
"steps": [
|
||
(10, "OP-KIT", "齐套检验", "备料区", 5, 10, 5, 0),
|
||
(20, "OP-SUBASSY", "部装", "装配台", 30, 60, 15, 15),
|
||
(30, "OP-ASSY-1", "总装一段", "装配台", 20, 45, 10, 10),
|
||
(40, "OP-ASSY-2", "总装二段", "装配台", 20, 45, 10, 10),
|
||
(50, "OP-WIRE", "电气接线", "接线工位", 10, 30, 10, 5),
|
||
(60, "OP-TEST", "功能测试", "测试台", 10, 25, 15, 5),
|
||
(70, "OP-FINAL", "终检包装", "检验台", 5, 15, 5, 0),
|
||
],
|
||
},
|
||
{
|
||
"code": "TPL-RAIL-ASSY", "name": "城轨机构装配(部装锚瓶颈)", "category": "装配",
|
||
"keywords": "城轨,机构,部装,总装,干线,门机构",
|
||
"description": "现场完整生产路线形态:部装(瓶颈,≈45分/件)→ 总装1…N 串联(≈30分/件)→ 检验交付;"
|
||
"BOM 几乎全部消耗在首道部装",
|
||
"asset_title": "城轨机构装配路线模板",
|
||
"steps": [
|
||
(10, "OP-SUBASSY", "机构部装", "共享装配工位", 40, 50, 15, 15),
|
||
(20, "OP-ASSY-1", "总装一", "共享装配工位", 25, 35, 10, 10),
|
||
(30, "OP-ASSY-2", "总装二", "共享装配工位", 25, 35, 10, 10),
|
||
(40, "OP-ASSY-3", "总装三", "共享装配工位", 25, 35, 10, 10),
|
||
(50, "OP-INSPECT", "检验交付", "检验台", 10, 20, 5, 0),
|
||
],
|
||
},
|
||
]
|
||
|
||
|
||
def seed_templates() -> int:
|
||
"""内置模板落库(幂等);返回模板总数。资产互链:按标题解析 assetId。"""
|
||
from server.db.database import get_session
|
||
from server.db.models import RoutingTemplate, RoutingTemplateStep
|
||
|
||
# 标题 → assetId(知识资产互链)
|
||
asset_by_title: dict[str, str] = {}
|
||
try:
|
||
from server.knowledge.assets import get_knowledge
|
||
for a in get_knowledge().assets:
|
||
asset_by_title[a.get("title") or ""] = a.get("assetId") or ""
|
||
except Exception:
|
||
pass
|
||
|
||
with get_session() as session:
|
||
existing = {t.code for t in session.execute(select(RoutingTemplate).where(
|
||
RoutingTemplate.tenant_uuid == "platform",
|
||
RoutingTemplate.deleted == 0,
|
||
)).scalars()}
|
||
for tpl in _BUILTIN:
|
||
if tpl["code"] in existing:
|
||
continue
|
||
row = RoutingTemplate(
|
||
tenant_uuid="platform",
|
||
code=tpl["code"], name=tpl["name"], industry="machining",
|
||
category=tpl["category"], keywords=tpl["keywords"],
|
||
description=tpl["description"],
|
||
asset_id=asset_by_title.get(tpl.get("asset_title") or "", ""),
|
||
source="builtin",
|
||
)
|
||
session.add(row)
|
||
session.flush()
|
||
for (seq, code, name, eq, low, high, setup, chg) in tpl["steps"]:
|
||
session.add(RoutingTemplateStep(
|
||
tenant_uuid="platform",
|
||
template_id=row.id, seq=seq, operation_code=code,
|
||
operation_name=name, equipment_type=eq,
|
||
std_min_low=float(low), std_min_high=float(high),
|
||
setup_min=float(setup), changeover_min=float(chg),
|
||
))
|
||
session.commit()
|
||
return len(session.execute(select(RoutingTemplate).where(
|
||
RoutingTemplate.tenant_uuid == "platform",
|
||
RoutingTemplate.deleted == 0,
|
||
)).scalars().all())
|
||
|
||
|
||
def list_templates() -> list[dict[str, Any]]:
|
||
"""模板清单(含步骤)。"""
|
||
from server.db.database import get_session
|
||
from server.db.models import RoutingTemplate, RoutingTemplateStep
|
||
identity = get_identity()
|
||
seed_templates()
|
||
with get_session() as session:
|
||
out = []
|
||
for t in session.execute(select(RoutingTemplate).where(
|
||
RoutingTemplate.tenant_uuid.in_(("platform", identity.tenant_uuid)),
|
||
RoutingTemplate.deleted == 0,
|
||
).order_by(RoutingTemplate.id)).scalars():
|
||
steps = session.execute(
|
||
select(RoutingTemplateStep).where(
|
||
RoutingTemplateStep.tenant_uuid == t.tenant_uuid,
|
||
RoutingTemplateStep.template_id == t.id,
|
||
RoutingTemplateStep.deleted == 0,
|
||
)
|
||
.order_by(RoutingTemplateStep.seq)).scalars().all()
|
||
out.append(_template_dict(t, steps))
|
||
return out
|
||
|
||
|
||
def _template_dict(t, steps) -> dict[str, Any]:
|
||
return {
|
||
"code": t.code, "name": t.name, "industry": t.industry,
|
||
"category": t.category, "keywords": t.keywords.split(","),
|
||
"description": t.description, "assetId": t.asset_id, "source": t.source,
|
||
"steps": [{
|
||
"seq": s.seq, "operationCode": s.operation_code, "operationName": s.operation_name,
|
||
"equipmentType": s.equipment_type,
|
||
"stdMinLow": s.std_min_low, "stdMinHigh": s.std_min_high,
|
||
"stdMinDefault": round((s.std_min_low + s.std_min_high) / 2, 1),
|
||
"setupMin": s.setup_min, "changeoverMin": s.changeover_min,
|
||
} for s in steps],
|
||
}
|
||
|
||
|
||
def get_template(code: str) -> dict[str, Any] | None:
|
||
return next((t for t in list_templates() if t["code"] == code), None)
|
||
|
||
|
||
def recommend_templates(text: str, top_k: int = 3) -> list[dict[str, Any]]:
|
||
"""按产品名/描述关键词推荐模板(缺路线时向导用)。"""
|
||
text = (text or "").strip()
|
||
scored: list[tuple[float, dict]] = []
|
||
for t in list_templates():
|
||
score = 0.0
|
||
for kw in t["keywords"]:
|
||
if kw and kw in text:
|
||
score += 2.0
|
||
if t["category"] in text:
|
||
score += 1.5
|
||
for step in t["steps"]:
|
||
if step["operationName"] and step["operationName"] in text:
|
||
score += 0.5
|
||
if score > 0:
|
||
scored.append((score, t))
|
||
scored.sort(key=lambda x: -x[0])
|
||
hits = [dict(t, matchScore=s) for s, t in scored[:top_k]]
|
||
if not hits:
|
||
# 无关键词命中 → 用 RAG 检索工艺模式知识反查模板
|
||
try:
|
||
from server.knowledge.assets import get_knowledge
|
||
from server.knowledge.retrieval import hybrid_search
|
||
rag_hits = hybrid_search(get_knowledge().iter_search_units(), f"{text} 工艺路线", top_k=3)
|
||
asset_ids = {h.get("assetId") for h in rag_hits}
|
||
hits = [dict(t, matchScore=0.5) for t in list_templates()
|
||
if t.get("assetId") in asset_ids][:top_k]
|
||
except Exception:
|
||
hits = []
|
||
return hits
|
||
|
||
|
||
def apply_template_to_product(world: dict[str, Any], template_code: str,
|
||
product_code: str, product_name: str = "") -> dict[str, Any]:
|
||
"""把模板实例化为产品柔性工艺路线(工时取区间中值,来源=模板)。
|
||
|
||
调用方负责 P2 确认与落盘;返回摘要。
|
||
"""
|
||
tpl = get_template(template_code)
|
||
if not tpl:
|
||
raise ValueError(f"未找到工艺模板:{template_code}")
|
||
pn = product_name or product_code
|
||
|
||
# 已有路线 → 先清掉该产品旧步骤(模板重放)
|
||
world["flexRoutings"] = [r for r in world.get("flexRoutings") or []
|
||
if r.get("productCode") != product_code]
|
||
ops = {o["code"]: o for o in world.get("flexOperations") or []}
|
||
equipment = world.get("flexEquipment") or []
|
||
added_ops = 0
|
||
for step in tpl["steps"]:
|
||
code = step["operationCode"]
|
||
if code not in ops:
|
||
world.setdefault("flexOperations", []).append({
|
||
"code": code, "name": step["operationName"],
|
||
"isBottleneck": False, "changeoverMin": step["changeoverMin"],
|
||
})
|
||
ops[code] = world["flexOperations"][-1]
|
||
added_ops += 1
|
||
# 无能力设备时给运行中设备补该能力(能力池共享;标推断)
|
||
if not any(code in (e.get("capabilities") or []) for e in equipment):
|
||
for e in equipment:
|
||
if e.get("status") == "RUNNING":
|
||
e.setdefault("capabilities", []).append(code)
|
||
e.setdefault("opStdTime", {})[code] = step["stdMinDefault"]
|
||
world.setdefault("flexRoutings", []).append({
|
||
"productCode": product_code, "productName": pn,
|
||
"seq": step["seq"], "operationCode": code,
|
||
"requireMold": False,
|
||
"stdTimePerUnit": step["stdMinDefault"],
|
||
"stdTimeSource": "模板",
|
||
})
|
||
# 班组资格覆盖:模板工序并入通用班组(无则新建),避免 NO_TEAM 卡死
|
||
tpl_ops = [s["operationCode"] for s in tpl["steps"]]
|
||
teams = world.get("flexTeams") or []
|
||
generic = next((tm for tm in teams if tm.get("code") == "T-TPL"), None)
|
||
if generic is None and teams is not None:
|
||
generic = {"code": "T-TPL", "name": "模板工序班组(推断)", "memberCount": 4,
|
||
"supportOps": [], "skillLevel": "L2"}
|
||
world.setdefault("flexTeams", []).append(generic)
|
||
if generic is not None:
|
||
for code in tpl_ops:
|
||
if code not in generic["supportOps"]:
|
||
generic["supportOps"].append(code)
|
||
# 产品主数据缺失则补
|
||
if not any(m.get("code") == product_code for m in world.get("flexMaterials") or []):
|
||
world.setdefault("flexMaterials", []).append({
|
||
"code": product_code, "name": pn, "type": "FINISHED_PRODUCT", "unit": "件",
|
||
"stock": 0, "inTransit": 0, "safetyStock": 0, "procurementLeadTime": 0,
|
||
})
|
||
return {"template": tpl["code"], "templateName": tpl["name"],
|
||
"productCode": product_code, "steps": len(tpl["steps"]),
|
||
"addedOperations": added_ops, "assetId": tpl.get("assetId") or ""}
|