209 lines
6.3 KiB
Python
209 lines
6.3 KiB
Python
|
|
#!/usr/bin/env python
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
实验流程表格生成脚本
|
|||
|
|
- 读取传入的 experimentProcess JSON
|
|||
|
|
- 将数据转换为包含合并信息的脚本表格描述
|
|||
|
|
- 输出格式与应用中的 scriptTable 占位符兼容
|
|||
|
|
- 额外生成脚本图表(scriptChartX)需要的数据,便于测试
|
|||
|
|
|
|||
|
|
可通过环境变量定制行为:
|
|||
|
|
- TABLE_TOKEN:目标占位符(默认 scriptTable1)
|
|||
|
|
- TABLE_START_ROW:相对占位符的起始行偏移(整数,默认 2)
|
|||
|
|
- TABLE_START_COL:相对占位符的起始列偏移(整数,默认 0)
|
|||
|
|
- TABLE_INCLUDE_HEADERS:为 true/1/yes 时输出 headers 行
|
|||
|
|
- CHART_TOKEN_PREFIX:脚本图表占位符前缀(默认 scriptChart)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
from typing import Any, Dict, List, Tuple
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _read_all_stdin() -> str:
|
|||
|
|
try:
|
|||
|
|
if sys.stdin and not sys.stdin.closed and not sys.stdin.isatty():
|
|||
|
|
return sys.stdin.read()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _load_payload() -> Dict[str, Any]:
|
|||
|
|
raw = _read_all_stdin().strip()
|
|||
|
|
|
|||
|
|
if not raw and len(sys.argv) > 1:
|
|||
|
|
arg = sys.argv[1]
|
|||
|
|
if os.path.exists(arg) and os.path.isfile(arg):
|
|||
|
|
with open(arg, "r", encoding="utf-8") as fh:
|
|||
|
|
raw = fh.read()
|
|||
|
|
else:
|
|||
|
|
raw = arg
|
|||
|
|
|
|||
|
|
if not raw:
|
|||
|
|
raw = os.environ.get("EXPERIMENT_JSON", "").strip()
|
|||
|
|
|
|||
|
|
if not raw:
|
|||
|
|
raw = "{}"
|
|||
|
|
|
|||
|
|
data = json.loads(raw)
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
raise ValueError("experiment JSON must be a dict")
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _include_headers() -> bool:
|
|||
|
|
flag = os.environ.get("TABLE_INCLUDE_HEADERS", "").strip().lower()
|
|||
|
|
return flag in {"1", "true", "yes", "on"}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _parse_float(value: Any) -> float:
|
|||
|
|
try:
|
|||
|
|
if isinstance(value, (int, float)):
|
|||
|
|
return float(value)
|
|||
|
|
if isinstance(value, str):
|
|||
|
|
cleaned = value.replace("%", "").replace("°C", "").replace("RPM", "").strip()
|
|||
|
|
return float(cleaned)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
return float("nan")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_table_spec(exp: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
# 目标占位符,可通过环境变量覆盖,默认写入 {scriptTable1}
|
|||
|
|
token = os.environ.get("TABLE_TOKEN", "scriptTable1")
|
|||
|
|
# 当模板中的占位符位于表头时,默认向下偏移两行再开始写入
|
|||
|
|
row_offset = int(os.environ.get("TABLE_START_ROW", "2") or 2)
|
|||
|
|
col_offset = int(os.environ.get("TABLE_START_COL", "0") or 0)
|
|||
|
|
|
|||
|
|
headers: List[Any] = exp.get("headers") or []
|
|||
|
|
rows: List[List[Any]] = exp.get("rows") or []
|
|||
|
|
|
|||
|
|
cells: List[Dict[str, Any]] = []
|
|||
|
|
cursor_row = 0
|
|||
|
|
|
|||
|
|
if headers and _include_headers():
|
|||
|
|
# 需要表头时,把 headers 行写成首行单元格
|
|||
|
|
for ci, title in enumerate(headers):
|
|||
|
|
cells.append({"row": cursor_row, "col": ci, "value": title})
|
|||
|
|
cursor_row += 1
|
|||
|
|
|
|||
|
|
idx = 0
|
|||
|
|
while idx < len(rows):
|
|||
|
|
row = rows[idx] if isinstance(rows[idx], list) else [rows[idx]]
|
|||
|
|
label = str(row[0]) if row else ""
|
|||
|
|
span = 1
|
|||
|
|
while idx + span < len(rows):
|
|||
|
|
nxt = rows[idx + span]
|
|||
|
|
if not isinstance(nxt, list):
|
|||
|
|
break
|
|||
|
|
if (len(nxt) > 0 and nxt[0] == label) and label != "":
|
|||
|
|
# 连续多行的首列内容相同时,记录合并行数
|
|||
|
|
span += 1
|
|||
|
|
else:
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
cells.append({
|
|||
|
|
"row": cursor_row,
|
|||
|
|
"col": 0,
|
|||
|
|
"value": label,
|
|||
|
|
"rowspan": span,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
for offset in range(span):
|
|||
|
|
values = rows[idx + offset]
|
|||
|
|
if not isinstance(values, list):
|
|||
|
|
values = [values]
|
|||
|
|
# 从第二列开始依次写入实际数据,不需要列合并
|
|||
|
|
for col_idx, val in enumerate(values[1:], start=1):
|
|||
|
|
cells.append({
|
|||
|
|
"row": cursor_row + offset,
|
|||
|
|
"col": col_idx,
|
|||
|
|
"value": val,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
cursor_row += span
|
|||
|
|
idx += span
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"token": token,
|
|||
|
|
"startRow": row_offset,
|
|||
|
|
"startCol": col_offset,
|
|||
|
|
# cells 是最终交给应用的单元格描述列表
|
|||
|
|
"cells": cells,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_chart_specs(exp: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|||
|
|
"""Return a fixed set of mock charts for testing.
|
|||
|
|
|
|||
|
|
This ignores the experiment payload and always returns data for
|
|||
|
|
scriptChart1/2/3 so the template can render charts during development.
|
|||
|
|
"""
|
|||
|
|
prefix = os.environ.get("CHART_TOKEN_PREFIX", "scriptChart")
|
|||
|
|
# Simple step axis
|
|||
|
|
steps = [1, 2, 3, 4, 5, 6]
|
|||
|
|
|
|||
|
|
charts: List[Dict[str, Any]] = [
|
|||
|
|
{
|
|||
|
|
"token": f"{prefix}1",
|
|||
|
|
"title": "转速趋势(测试)",
|
|||
|
|
"xLabel": "步骤",
|
|||
|
|
"yLabel": "转速 (RPM)",
|
|||
|
|
"kind": "line",
|
|||
|
|
"grid": True,
|
|||
|
|
"series": [
|
|||
|
|
{"label": "输入转速", "x": steps, "y": [900, 1200, 1500, 1800, 2000, 2200]},
|
|||
|
|
{"label": "曲轴转速", "x": steps, "y": [150, 200, 260, 300, 330, 360]},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"token": f"{prefix}2",
|
|||
|
|
"title": "流量分布(测试)",
|
|||
|
|
"xLabel": "步骤",
|
|||
|
|
"yLabel": "流量 (LPM)",
|
|||
|
|
"kind": "bar",
|
|||
|
|
"grid": True,
|
|||
|
|
"series": [
|
|||
|
|
{"label": "流量", "x": steps, "y": [120, 140, 160, 180, 200, 220]},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"token": f"{prefix}3",
|
|||
|
|
"title": "压力曲线(测试)",
|
|||
|
|
"xLabel": "步骤",
|
|||
|
|
"yLabel": "压力 (MPa)",
|
|||
|
|
"kind": "line",
|
|||
|
|
"grid": True,
|
|||
|
|
"series": [
|
|||
|
|
{"label": "压力", "x": steps, "y": [8.0, 9.5, 10.2, 10.8, 11.3, 11.8]},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return charts
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
try:
|
|||
|
|
try:
|
|||
|
|
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
payload = _load_payload()
|
|||
|
|
table_spec = build_table_spec(payload)
|
|||
|
|
chart_specs = build_chart_specs(payload)
|
|||
|
|
result = {"tables": [table_spec]}
|
|||
|
|
if chart_specs:
|
|||
|
|
result["charts"] = chart_specs
|
|||
|
|
print(json.dumps(result, ensure_ascii=False))
|
|||
|
|
return 0
|
|||
|
|
except Exception as exc:
|
|||
|
|
print(f"error: {exc}", file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
sys.exit(main())
|