PCM_Report/tablecreate.py

209 lines
6.3 KiB
Python
Raw Normal View History

2025-12-11 14:32:31 +08:00
#!/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())