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()) |