Migrate product-data exports to skill scripts

This commit is contained in:
wuyang6
2026-05-11 19:43:17 +08:00
parent a68302659d
commit 7dc7fa2e52
8 changed files with 112 additions and 52 deletions
@@ -1,7 +1,5 @@
from __future__ import annotations
from pathlib import Path
from product_data_portable import (
DEFAULT_REQUEST_ID,
DEFAULT_TIMESTAMP_STEP_MS,
@@ -9,6 +7,7 @@ from product_data_portable import (
emit_success,
load_json_payload,
normalize_dataset_draft,
resolve_portable_path,
)
@@ -17,15 +16,21 @@ def main() -> int:
payload = load_json_payload()
draft_text = str(payload.get("draft_text") or "")
if not draft_text and payload.get("draft_path"):
draft_text = Path(str(payload["draft_path"])).expanduser().read_text(encoding="utf-8")
draft_text = resolve_portable_path(str(payload["draft_path"])).read_text(encoding="utf-8")
source_type = str(payload.get("source_type") or "generated")
confirmed_plan_id = str(payload.get("confirmed_plan_id") or "").strip()
if source_type == "generated" and not confirmed_plan_id:
raise ValueError("confirmed_plan_id is required for generated data")
result = normalize_dataset_draft(
draft_text,
batch_id=str(payload.get("batch_id") or DEFAULT_REQUEST_ID),
source_type=str(payload.get("source_type") or "generated"),
source_type=source_type,
base_timestamp=payload.get("base_timestamp"),
timestamp_step_ms=int(payload.get("timestamp_step_ms") or DEFAULT_TIMESTAMP_STEP_MS),
default_request_id=str(payload.get("default_request_id") or DEFAULT_REQUEST_ID),
)
if confirmed_plan_id:
result["confirmed_plan_id"] = confirmed_plan_id
emit_success(result)
return 0
except Exception as exc: # noqa: BLE001 - CLI 需要把错误稳定转成 JSON
@@ -9,6 +9,7 @@ from __future__ import annotations
import argparse
import csv
import json
import os
import re
import sys
import time
@@ -79,7 +80,7 @@ def load_records(payload: dict[str, Any]) -> list[dict[str, Any]]:
def read_records_file(path: str) -> list[dict[str, Any]]:
file_path = Path(path).expanduser()
file_path = resolve_portable_path(path)
text = file_path.read_text(encoding="utf-8")
if file_path.suffix.lower() == ".jsonl":
records: list[dict[str, Any]] = []
@@ -814,40 +815,82 @@ def default_output_path(payload: dict[str, Any]) -> str:
output_format = str(payload.get("output_format") or "jsonl")
filename = "records.json" if output_format == "json" else "records.jsonl"
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
return payload["output_path"].strip()
return str(resolve_portable_path(payload["output_path"].strip()))
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
return str(Path(payload["output_dir"]).expanduser() / filename)
return str(Path("output") / filename)
return str(resolve_portable_path(payload["output_dir"].strip()) / filename)
return str(default_output_root() / filename)
def default_table_output_path(payload: dict[str, Any]) -> str:
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
return payload["output_path"].strip()
return str(resolve_portable_path(payload["output_path"].strip()))
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
return str(Path(payload["output_dir"]).expanduser() / "records.csv")
return str(resolve_portable_path(payload["output_dir"].strip()) / "records.csv")
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
return str(Path(payload["records_path"]).expanduser().with_name("records.csv"))
return str(Path("output") / "records.csv")
return str(resolve_portable_path(payload["records_path"].strip()).with_name("records.csv"))
return str(default_output_root() / "records.csv")
def default_training_output_path(payload: dict[str, Any]) -> str:
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
return payload["output_path"].strip()
return str(resolve_portable_path(payload["output_path"].strip()))
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
return str(Path(payload["output_dir"]).expanduser() / "training.jsonl")
return str(resolve_portable_path(payload["output_dir"].strip()) / "training.jsonl")
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
return str(Path(payload["records_path"]).expanduser().with_name("training.jsonl"))
return str(Path("output") / "training.jsonl")
return str(resolve_portable_path(payload["records_path"].strip()).with_name("training.jsonl"))
return str(default_output_root() / "training.jsonl")
def default_planning_eval_output_path(payload: dict[str, Any]) -> str:
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
return payload["output_path"].strip()
return str(resolve_portable_path(payload["output_path"].strip()))
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
return str(Path(payload["output_dir"]).expanduser() / "eval_planning.csv")
return str(resolve_portable_path(payload["output_dir"].strip()) / "eval_planning.csv")
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
return str(Path(payload["records_path"]).expanduser().with_name("eval_planning.csv"))
return str(Path("output") / "eval_planning.csv")
return str(resolve_portable_path(payload["records_path"].strip()).with_name("eval_planning.csv"))
return str(default_output_root() / "eval_planning.csv")
def default_output_root() -> Path:
"""返回 portable 脚本默认输出目录。
在 ZK Data Agent 中,python_exec 的 cwd 是当前会话 scratchpad
通过 PYTHON_EXEC_SCRATCHPAD 可以定位同级 output 目录。离开平台独立
运行时,保持原来的相对 output/ 行为。
"""
scratchpad = os.environ.get("PYTHON_EXEC_SCRATCHPAD")
if scratchpad:
return Path(scratchpad).expanduser().parent / "output"
return Path("output")
def resolve_portable_path(path: str) -> Path:
"""解析 portable 脚本路径,并兼容平台逻辑路径。
平台内运行时,把 output/、scratchpad/、input/ 映射到当前会话目录;
其他相对路径仍按当前 cwd 解析,便于普通 Python 环境复用。
"""
raw_path = Path(path).expanduser()
if raw_path.is_absolute():
return raw_path
scratchpad = os.environ.get("PYTHON_EXEC_SCRATCHPAD")
if not scratchpad:
return raw_path
session_root = Path(scratchpad).expanduser().parent
parts = raw_path.parts
if not parts:
return raw_path
head, *tail = parts
tail_path = Path(*tail) if tail else Path()
if head in {"output", "outputs"}:
return session_root / "output" / tail_path
if head in {"scratchpad", "scratch"}:
return Path(scratchpad).expanduser() / tail_path
if head in {"input", "inputs"}:
return session_root / "input" / tail_path
return raw_path
def normalize_target_expression(target: str) -> str: