📊 面料小田 · 数据接入到实时看板

从 ERP 数据库查询到 LOOP 发布 / BI 看板 的完整流程

版本 1.0 · 恒田面料部 / 印染 · 2026年7月

📑 目录

  1. 总体架构
  2. 第1章 · 数据接入:环思ERP查询
  3. 第2章 · 在线监测:传感器 / IoT 数据
  4. 第3章 · 生成 HTML 看板
  5. 第4章 · 发布到 LOOP(静态报告)
  6. 第5章 · BI 看板模式(交互刷新)
  7. 第6章 · 定时推送 / 日报
  8. 第7章 · Excel 文件交付
  9. 常见问题

🏗️ 总体架构

🏭 车间传感器
📡 Modbus RTU / DTL645
🗄️ 环思 ERP (HSdyeingERP)

👨‍💼 人工录入(产量/质量/出入库)
🗄️ 环思 ERP


🤖 面料小田
Python + pymssql
📋 LOOP 静态看板
🔄 BI 交互看板
📎 Excel 文件
📱 钉钉推送

恒田数据链路全景

数据来源采集方式入 ERP 方式小田查询方式
产量(染色/定型/后整理)刷卡机报工 / 人工录入ppTrackOutput / rsRegistrationDtlpymssql SQL
品质(异常/批色/物测)品管员录入ycAbnormalQualityRHdr / qmColorMatchHdr / qmTesthdrpymssql SQL
库存(成品/坯布/染化料)扫码出入库vwmmSTInStore / mmFPTotalStock / vwmmDCTotalStockpymssql SQL
能耗(水电蒸汽天然气)IoT 传感器mnEquipmentEnergyRealTime(30分钟级)
kbEquipmentEnergyUsage(日汇总)
pymssql SQL
水表IoT 传感器(Modbus RTU)COMModbus_RTU_SB_52/53 → ERPpymssql SQL
人力/班组HR 排班smUser / ppWorkerGrouppymssql SQL

核心结论:后台数据是 真实传感器数据,不是 Mock。能耗数据通过 Modbus RTU / DTL645 等工业协议,每 30 分钟采集一次写入 ERP。

📡 第1章 · 数据接入:环思ERP查询

1.1 数据库连接

# 连接环思印染ERP import pymssql # 密码从独立文件读取,不硬编码 with open('/opt/hengtian/secrets/hsdyeing_readonly.password') as f: pw = f.read().strip() conn = pymssql.connect( server='61.160.83.154', port=2433, user='readonly_user', password=pw, database='HSdyeingERP', charset='utf8' )
⚠️ Python 环境:系统 python3 没有 pymssql 模块,必须用虚拟环境:/opt/hengtian/.venv/bin/python3

1.2 核心表速查

业务域表名核心字段
订单vwsdOrdersOrderNo, sCustomerName, sSourceName(涤/棉), dDeliveryDate
工卡psWorkFlowCardsCardNo, sOrderNo, sStatus, sRawFabricNo(缸号), nPlanOutputQty, nFactOutputQty
染色产量ppTrackOutputnTrackQty(Kg), nTrackQtyEx(匹), tTrackTime
批色qmColorMatchHdr/DtlsResult('OK'=通过), sCardNo
品质异常ycAbnormalQualityRHdr/DtlsDefect, nRepineQty, sDutyDept(责任部门, 分号分隔)
成品入库vwmmSTInStorenInQty(Kg), nInPkgQty(匹), sStoreNo='G02'
成品出库vwmmSTOutStorenStoreOutQty(Kg), sStoreOutNo
能耗(日汇总)fiProduceDayCostnEL_Amount(电费), nVA_Amount(蒸汽费), nGas_Amount, nWater_Amount
能耗(实时)mnEquipmentEnergyRealTimenRealValue, tRealTime(30分钟间隔)
能耗(日读数)kbEquipmentEnergyUsagenStartQty, nEndQty, nQty(当日用量)
成本pbCommonDataCostSTData150列, 含坯布/染料/助剂/工资/水电蒸汽/五金/委外等
⚠️ 关键陷阱:
  • fiProduceDayCost 中电费是 nEL_Amount,不是 nElec_Amount;蒸汽是 nVA_Amount,不是 nSteam_Amount
  • 品质异常 sDutyDept 可能包含多个部门(分号分隔),计算时需均分
  • 返修工卡判断:卡号后缀含 -A-B 为返修
  • 时间口径默认 早 07:30 ~ 次日 07:30
  • 工卡表 psWorkFlowCard 没有 sColorName/sColorNo,需 JOIN tmColor

1.3 标准查询模板

查当日染色产量

SELECT e.sEquipmentNo AS 染缸编号, ISNULL(SUM(p.nTrackQty), 0) AS 产量_Kg FROM ppTrackOutput p JOIN emEquipment e ON p.uemEquipmentGUID = e.uGUID JOIN ppTrackJob j ON p.uppTrackJobGUID = j.uGUID JOIN psWorkFlowCard w ON j.upsWorkFlowCardGUID = w.uGUID WHERE e.sEquipmentGroup = 'HT' -- HT=涤, HD=棉 AND e.sEquipmentNo NOT LIKE 'DX%' -- 排除定型机 AND p.tTrackTime >= '2026-07-21 07:30:00' AND p.tTrackTime < '2026-07-22 07:30:00' GROUP BY e.sEquipmentNo ORDER BY 产量_Kg DESC

查当日能耗

SELECT SUM(nWater_Amount) AS 水费, SUM(nEL_Amount) AS 电费, SUM(nVA_Amount) AS 蒸汽费, SUM(nGas_Amount) AS 天然气费 FROM fiProduceDayCost WHERE dBillDate = '2026-07-21'

1.4 染化料成本查询

-- 染助剂出库数据(ERP成本表不包含,需从出库模块另取) SELECT sMaterialTypeName, SUM(nAmount) AS 金额 FROM vwmmDCOutStore WHERE tStoreOutTime >= '2026-07-21 07:30:00' AND tStoreOutTime < '2026-07-22 07:30:00' GROUP BY sMaterialTypeName
💡 经验值:染助剂成本约占每日总能耗的 50-55%,不能遗漏。完整能耗公式 = 工业水 + 电 + 蒸汽 + 天然气 + 污水 + 染料 + 助剂。

🔌 第2章 · 在线监测:传感器 / IoT 数据

已接入传感器清单

类型协议点位数量示例
电表COMModbus_RTU_DB / DTL645 / DTLONE / TCPModBus_MW~30+ 个各染缸电表(H13~H39 / Q1~Q11)、定型机电表 1#~8#
水表COMModbus_RTU_SB~8 个[52]棉总用水水表、[53]涤总用水水表、[91]/[92]回用水池
蒸汽表COMModbus_RTU_ZQ~5 个南/北涤车间蒸汽表、棉车间蒸汽表
天然气表COMModbus_RTU_TRQ / TCPModBus_MW_TRQ~7 个定型机 2#/5#/6#/7#/8# 天然气表、锅炉天然气表

2.1 实时数据表(mnEquipmentEnergyRealTime)

每 30 分钟采集一次,记录所有表计的读数。数据字段:

sModulCode = 'COMModbus_RTU_SB_52' -- 表计编号 sModulName = '[52]棉总用水水表' -- 表计中文名 sEngClass = 'WA' -- WA=水, EC=电, VA=蒸汽, NA=天然气 nRealValue = 42417.62 -- 实时读数 tRealTime = 2026-07-21 14:30:00 -- 采集时间

2.2 日汇总表(kbEquipmentEnergyUsage)

每日 08:00 自动生成一条汇总记录(按 07:30~07:30 会计口径):

sWorkCentreName = '染色车间' sEquipmentNo = '002' sEquipmentName = '涤车间总表' sClass = '工业水(吨)' sUserModuleName = '[53]涤总用水水表' nStartQty = 158940.00 -- 期初读数 nEndQty = 159020.50 -- 期末读数 nQty = 80.50 -- 当日用量 tStartTime = 2026-07-20 07:30:00 tEndTime = 2026-07-21 07:30:00

2.3 判断数据真实性的方法

特征真实数据表现示例
读数递增期初≠期末,数字连续增长1#定型机电表 1219390.8 → 1220161.2
工业协议使用标准协议名称COMModbus_RTU = RS485 Modbus RTU
设备关联表计编号与实际设备对应[68]1#定型机电表 → 设备 DX01
规律采样固定的时间间隔30分钟一次,持续数年
小幅波动连续两次读数差反映真实消耗棉总水表 42416 → 42417.62(半小时增1.62吨)

2.4 水表查询(车间级)

-- 染色车间用水量(涤+棉总水表 + 回用水池) SELECT dReportDate, sUserModuleName, nQty FROM kbEquipmentEnergyUsage WHERE sClass = '工业水(吨)' AND sWorkCentreName = '染色车间' AND (sUserModuleName LIKE '%涤总用水水表%' OR sUserModuleName LIKE '%棉总用水水表%') ORDER BY dReportDate DESC
🧠 记忆提示:染色车间只看「涤总用水水表」和「棉总用水水表」,不包含回用水池、染缸水表、水洗机水表等其他水表。

🎨 第3章 · 生成 HTML 看板

3.1 准备工作

mkdir -p /opt/hengtian/workspace/hengtian-fabric/outbox

3.2 三步生成法

1 查数据 — 用 pymssql 从 ERP 查数据,存为 JSON

#!/opt/hengtian/.venv/bin/python3 import pymssql, json # ... 连接ERP,执行查询 ... data = {'total_kgs': 12345, 'orders': [...], 'defects': [...]} # 保存为JSON,供HTML嵌入 with open('data.json', 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, default=str)

2 写 HTML — 单文件自包含,CSS/JS 内联,数据嵌入 <script>

<!-- 看板.html 核心结构 --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>生产状态看板</title> <style> /* 所有样式内联,不用外部资源 */ body { font-family: ...; } </style> </head> <body> <div id="summary"></div> <div id="details"></div> <script> // 数据嵌入为JSON const DATA = JSON.parse('{"total_kgs": 12345, ...}'); // 渲染函数 function render(data) { ... } render(DATA); </script> </body> </html>

3 效果预览 — 直接双击或用浏览器打开即可查看

3.3 看板设计要点

要素推荐做法
布局顶部汇总卡片(4~6个KPI)+ 下方明细表格,有导航Tab
配色🔴 >90天超期 / 🟡 >30天超期 / 🔵 正常 / 🟢 完成
搜索每个表格上方放搜索输入框,实时过滤
排序点击表头排序 asc/desc
响应式用 CSS flex/grid,在手机端也能看
单文件所有 CSS 和 JS 内联,不引用外部 CDN
文件大小控制在 2MB 以内
✅ 示例看板:需求表、生产进度、品质异常、成本分析 等看板模板可在面料小田工作区查找。

🌐 第4章 · 发布到 LOOP(静态报告)

4.1 什么是 LOOP

LOOP 是恒田内部的报告发布平台。HTML 看板上传到 LOOP 后,会获得一个固定链接,可供全员在公网访问。

地址说明
https://www.handagroup.ai/loop/<slug>公共查看页(含 LOOP 包装)
https://www.handagroup.ai/api/loop/reports/<slug>/html纯 HTML 直链(浏览器直接渲染)

4.2 发布步骤

1 准备 HTML 文件 — 单文件自包含看板(见第3章)

2 调用发布 API

#!/usr/bin/env python3 import os, json, requests TOKEN = os.environ['HANDA_LOOP_UPLOAD_TOKEN'] SLUG = 'production-dashboard-20260721' # 英文+数字+中划线 with open('/path/to/dashboard.html', 'r', encoding='utf-8') as f: html_content = f.read() resp = requests.post( 'https://www.handagroup.ai/api/loop/reports', json={ 'slug': SLUG, 'title': '生产进度看板 | 2026年7月21日', 'description': '纺织面料生产进度实时看板', 'owner': '恒田小兴', 'html': html_content, 'file_name': 'dashboard.html', 'tags': ['production', 'dashboard'], 'source_agent': 'hengtian-fabric', 'uploaded_by': '面料小田' }, headers={'Authorization': f'Bearer {TOKEN}'} ) print(resp.json())

3 验证发布结果

# 验证两个URL都能访问 curl -s -o /dev/null -w "%{http_code}" \ https://www.handagroup.ai/loop/production-dashboard-20260721 # 预期输出: 200 curl -s -o /dev/null -w "%{http_code}" \ https://www.handagroup.ai/api/loop/reports/production-dashboard-20260721/html # 预期输出: 200

4.3 发布注意事项

🚫 重要限制:
  • slug 不支持中文,只能用英文小写、数字和中划线
  • token 从环境变量读取,不要硬编码
  • API 只接受 JSON POST,不是 multipart/form-data
  • HTML 内容字段是 html,不是 html_content
  • 文件大小 不超过 2MB
  • LOOP 没有 DELETE 接口,取消需用新内容覆盖

4.4 取消(下架)看板

# 用最小化的取消页面覆盖原 slug cancel_html = '''<!DOCTYPE html> <html><body> <h1>已取消</h1> <p>此报告已被取消。</p> </body></html>''' requests.post('https://www.handagroup.ai/api/loop/reports', json={'slug': 'old-slug', 'html': cancel_html, 'owner': '恒田小兴'})

🔄 第5章 · BI 看板模式(交互刷新)

5.1 什么时候用 BI 看板

场景推荐模式说明
一次分享、固定报告LOOP 发布静态 HTML,公网可访问
需要点按钮刷新数据BI 看板Python 服务 + API 查 ERP,前端 JSON 更新
局域网内频繁查看BI 看板2000 端口起服务,省去每次重新发布
🚫 不要部署 BI 看板到 LOOP:LOOP 只存静态 HTML,刷新按钮会 404。BI 看板必须有后端 API 服务。

5.2 架构

📡 Python HTTP Server (port 19656)
├── GET / → 返回 index.html(含初始数据)
└── GET /api/refresh → 查ERP → 返回JSON → 前端更新DOM

5.3 后端启动

# 启动看板服务(绑定 0.0.0.0,局域网所有设备可访问) cd /opt/hengtian/workspace/hengtian-fabric source .venv/bin/activate python3 scripts/dashboard_server.py 19656 # 访问地址 http://服务器IP:19656/ -- 同一局域网下 http://127.0.0.1:19656/ -- 本机
# 停止服务 fuser -k 19656/tcp

5.4 前端刷新逻辑

function refreshData() { const btn = document.getElementById('refreshBtn'); btn.disabled = true; btn.textContent = '⏳ 刷新中...'; const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/refresh', true); xhr.timeout = 60000; // 60秒超时(ERP查询可能慢) xhr.onload = function() { try { const res = JSON.parse(xhr.responseText); if (res.ok) { initDashboard(res.data); // 重新渲染 } else { alert('刷新失败: ' + res.error); } } catch(e) { alert('解析失败'); } btn.disabled = false; btn.textContent = '🔄 刷新数据'; }; xhr.send(); }

5.5 后台运行(长期服务)

# 用面料小田的 cron job 机制启动后台服务 terminal(background=true, command='python3 scripts/dashboard_server.py 19656') # 或者使用 nohup nohup .venv/bin/python3 scripts/dashboard_server.py 19656 > logs/dashboard.log 2>&1 &

⏰ 第6章 · 定时推送 / 日报

6.1 创建定时任务

# 面料小田提供的 cron job 接口 cronjob(action='create', schedule='0 2 * * *', -- 每天 UTC 02:00 = 北京 10:00 prompt='查昨日产量数据并生成日报推送', skills=['erp-data-export', 'dingtalk-output-format'])
执行频率Cron 表达式北京时间
每天0 2 * * *10:00
每天0 7 * * *15:00
每隔 30 分钟30m每 30 分钟
每周一0 1 * * 1周一 09:00
每月1日0 2 1 * *每月1日 10:00

6.2 两种推送模式

no_agent 模式(推荐)

脚本直接执行,不消耗 LLM token。适合固定格式的数据推送:

cronjob(action='create', schedule='0 2 * * *', script='scripts/daily_production_push.py', no_agent=true)

LLM-agent 模式

Agent 阅读数据后汇总、分析、生成自然语言报告:

cronjob(action='create', schedule='0 1 * * 1', prompt='总结上周生产数据,分析异常趋势,生成周报摘要')

6.3 钉钉群推送

注意:cron job 的 deliver 参数对钉钉群聊不可靠。如需推到钉钉群,需用 群机器人 Webhook 主动 POST。

import requests # group webhook URL(需在钉钉群内添加机器人获取) WEBHOOK = 'https://oapi.dingtalk.com/robot/send?access_token=xxx' msg = { 'msgtype': 'markdown', 'markdown': { 'title': '📊 每日生产汇总', 'text': '''# 📊 每日生产汇总\n\n | 项目 | 数值 |\n |------|------|\n | 入库产量 | 32.5 吨 |\n | 出库产量 | 28.1 吨 |''' } } requests.post(WEBHOOK, json=msg)
💡 推送格式:钉钉机器人不渲染表格行。如果数据多,推荐 列表+emoji 格式,然后把明细放 LOOP 看板链接。

6.4 查看与管理任务

# 列出所有定时任务 cronjob(action='list') # 暂停任务 cronjob(action='pause', job_id='xxx') # 删除任务 cronjob(action='remove', job_id='xxx') # 立即执行一次 cronjob(action='run', job_id='xxx')

📎 第7章 · Excel 文件交付

7.1 生成 Excel 并发送

import openpyxl from openpyxl.styles import Font, PatternFill, Alignment, Border, Side wb = openpyxl.Workbook() ws = wb.active ws.title = '生产数据' # 表头样式 header_font = Font(name='微软雅黑', size=11, bold=true, color='FFFFFF') header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid') thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin')) # 写表头 headers = ['客户', '订单号', '颜色', '数量(Kg)', '交期'] for col, h in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=h) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') cell.border = thin_border # 写数据行(逐行不合并) for row_idx, row_data in enumerate(data_rows, 2): for col_idx, val in enumerate(row_data, 1): cell = ws.cell(row=row_idx, column=col_idx, value=val) cell.border = thin_border # 保存 output = '/opt/hengtian/workspace/hengtian-fabric/outbox/生产数据_20260721.xlsx' wb.save(output) print(f'✅ 已保存: {output}')

7.2 发送文件到钉钉会话

# 在最终回复中放置 MEDIA 标记 MEDIA:/opt/hengtian/workspace/hengtian-fabric/outbox/生产数据_20260721.xlsx

面料小田会自动将 MEDIA 路径的文件作为附件发送到当前钉钉会话。

📁 文件类型支持:.xlsx .xls .csv .pdf .html .docx .pptx .txt .md .png .jpg

❓ 常见问题

Q: 为什么我查到的数据是 0 或 NULL?

常见原因及排查:

  1. 时间口径不对 — 默认 07:30~次日07:30,不是自然日 00:00~24:00
  2. 字段名拼错 — 如 `nEL_Amount` 不是 `nElec_Amount`,`nVA_Amount` 不是 `nSteam_Amount`
  3. 表不存在 — 先 `SELECT TOP 0 * FROM 表名` 确认表存在
  4. 权限不足 — readonly_user 不能执行存储过程,只能用直接 SQL
  5. 数据库掉线 — ERP 服务器偶尔超时,加 3 次重试机制

Q: 传感器数据是实时的吗?

mnEquipmentEnergyRealTime 表每 30 分钟采集一次(不是真正的实时)。
kbEquipmentEnergyUsage 表每天 08:00 生成日汇总(07:30~07:30 口径)。

Q: 如何判断我的数据是正确可信的?

sanity check(合理性校验):

  • 单缸单班产量一般在 0.5~3.0 吨,如果某缸 >5 吨,可能单位输错了(公斤→吨)
  • 能耗 2,300~2,800 元/吨 是正常范围
  • 染助剂成本约占每日总能耗的 50~55%
  • 固定成本(工资、辅料等)约 16 万/天

Q: BI 看板和 LOOP 发布有什么区别?

LOOP 发布BI 看板
刷 新❌ 不能,静态页面✅ 点按钮刷新
访问公网可访问局域网内访问
数据更新需重新生成→重新发布调用 API 即可
适用一次性报告、分享给外部内部日常监控

Q: 我想定时把 Excel 报表发到钉钉群,怎么做?

两步:

  1. 写一个 Python 脚本,查 ERP → 生成 Excel → 通过钉钉群机器人 Webhook 发送链接
  2. cronjob(action='create', no_agent=true, script='xxx.py') 设定时间

注意:钉钉机器人不能直接发文件附件,只能发消息+链接。