Repository navigation
Aafeedback changes tsfm tools #633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4eae454
c1f406d
1f30953
08e817b
ebd792c
6b03609
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| [ | ||
| { | ||
| "feature_id": "efe_time_robust_norm_v1", | ||
| "name": "Invertible robust normalization", | ||
| "modality": "timeseries", | ||
| "interface": "fit_transform_inverse", | ||
| "class_name": "Transformation", | ||
| "invertible": true, | ||
| "code": "import numpy as np\nclass Transformation:\n \"\"\"Invertible robust per-channel normalization (median/IQR) for forecasting inputs.\"\"\"\n def fit(self, X, metadata):\n X = np.asarray(X, dtype=float)\n center = np.median(X, axis=0)\n q1, q3 = np.percentile(X, 25, axis=0), np.percentile(X, 75, axis=0)\n scale = np.where((q3 - q1) > 1e-8, (q3 - q1), 1.0)\n return {\"center\": center, \"scale\": scale}\n def transform(self, X, state):\n X = np.asarray(X, dtype=float)\n return (X - state[\"center\"]) / state[\"scale\"]\n def inverse_transform(self, Y, state):\n Y = np.asarray(Y, dtype=float)\n return Y * state[\"scale\"] + state[\"center\"]\n", | ||
| "provenance": "evolved", | ||
| "method": "EFE-Time", | ||
| "parent_feature_id": null, | ||
| "generation": 0, | ||
| "target_task": "tsfm_forecasting", | ||
| "target_model": null, | ||
| "dataset": null, | ||
| "output_type": "normalized_series", | ||
| "columns_added": [], | ||
| "columns_dropped": [], | ||
| "validity": { | ||
| "entry_points": true, | ||
| "no_inplace": true, | ||
| "invertible_ok": true, | ||
| "leakage_checked": true | ||
| }, | ||
| "tags": [ | ||
| "timeseries", | ||
| "EFE-Time", | ||
| "Future State Prediction" | ||
| ], | ||
| "status": "active", | ||
| "version": "1", | ||
| "created_by": "seed", | ||
| "created_at": "2026-06-20T00:00:00+00:00", | ||
| "kind": "transform", | ||
| "description": "Invertible per-channel robust normalization (median/IQR) for forecasting inputs." | ||
| }, | ||
| { | ||
| "feature_id": "abs_2nd_diff_mean", | ||
| "name": "abs_2nd_diff_mean", | ||
| "description": "Mean absolute second difference.", | ||
| "kind": "extractor", | ||
| "extractor_name": "abs_2nd_diff_mean", | ||
| "status": "active" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,32 +8,16 @@ | |
| from __future__ import annotations | ||
|
|
||
| import copy | ||
| from typing import Any, Dict, Optional | ||
| from typing import Optional | ||
|
|
||
|
|
||
| class FeatureValidationError(Exception): | ||
| pass | ||
|
|
||
|
|
||
| def load_program(code: str, class_name: str = "Transformation"): | ||
| """Exec a stored program string and return its Transformation class. | ||
|
|
||
| NOTE: executes code. In the benchmark/agent setting run this in a sandbox | ||
| (restricted globals, subprocess, or container). Here we exec in a fresh namespace | ||
| with a minimal import surface, matching how EFE executes candidate programs. | ||
| """ | ||
| ns: Dict[str, Any] = {} | ||
| try: | ||
| exec(code, ns) # noqa: S102 - intentional; sandbox in production | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ShuxinLin this is a main thing we disabled. |
||
| except Exception as e: | ||
| raise FeatureValidationError(f"program failed to import: {e}") | ||
| cls = ns.get(class_name) | ||
| if cls is None: | ||
| raise FeatureValidationError(f"no class '{class_name}' defined") | ||
| for m in ("fit", "transform"): | ||
| if not callable(getattr(cls, m, None)): | ||
| raise FeatureValidationError(f"missing required method '{m}'") | ||
| return cls | ||
| """Disabled: the server never executes feature-card source (it used to exec() it).""" | ||
| raise FeatureValidationError("executing feature-card code is disabled in the TSFM server") | ||
|
|
||
|
|
||
| def _no_inplace(before, after) -> bool: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,8 @@ | |
|
|
||
| import json | ||
| import os | ||
| import re | ||
| import tempfile | ||
| import uuid | ||
| from typing import List, Optional | ||
|
|
||
|
|
@@ -20,8 +22,36 @@ | |
| WORKDIR = os.environ.get("TSFM_WORKDIR", "/tmp/tsfm_work") | ||
|
|
||
|
|
||
| def _data_roots() -> List[str]: | ||
| """Directories a file pointer may resolve into: this server's WORKDIR, the system temp dir | ||
| and /tmp (code_exec workspaces, other servers' outputs), the Harbor shared workspace, and | ||
| artifacts/output (where agents save fine-tuned checkpoints via recipe save_to). | ||
| TSFM_DATA_ROOTS (os.pathsep-separated) replaces the default.""" | ||
| env = os.environ.get("TSFM_DATA_ROOTS") | ||
| roots = env.split(os.pathsep) if env else [ | ||
| WORKDIR, tempfile.gettempdir(), "/tmp", "/workspace-share", | ||
| os.path.join(os.getcwd(), "artifacts", "output")] # where agents save checkpoints | ||
| return [os.path.realpath(r) for r in roots if r] | ||
|
|
||
|
|
||
| def _path(ref: str) -> str: | ||
| return ref[7:] if ref.startswith("file://") else ref | ||
| """Resolve a file pointer, confined to the data roots. | ||
|
|
||
| Unconfined, every dataset_path read any file the server could open, e.g. | ||
| /proc/self/environ (the CouchDB password). realpath() follows symlinks, so a link | ||
| planted in a workspace cannot escape; URLs (http://, s3://) are refused. | ||
| """ | ||
| if "://" in ref and not ref.startswith("file://"): | ||
| raise ValueError("only local file pointers are supported") | ||
| p = os.path.realpath(ref[7:] if ref.startswith("file://") else ref) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confinement can still be bypassed. Suggested fix: open first, then validate what was opened. |
||
| if not any(p == r or p.startswith(r + os.sep) for r in _data_roots()): | ||
| raise ValueError(f"file pointer is outside the allowed data directories: {ref}") | ||
| return p | ||
|
|
||
|
|
||
| def _safe_name(name) -> str: | ||
| """Output file stem; plan step ids are agent-supplied, so strip path characters.""" | ||
| return re.sub(r"[^A-Za-z0-9_.-]", "_", str(name)).lstrip(".") or "result" | ||
|
|
||
|
|
||
| def _ensure_workdir(): | ||
|
|
@@ -91,7 +121,7 @@ def load_series( | |
| def write_series(obj, *, name: str = "result") -> str: | ||
| """Write a forecast/series/frame to a file pointer and return the ref.""" | ||
| _ensure_workdir() | ||
| p = os.path.join(WORKDIR, f"{name}_{uuid.uuid4().hex[:8]}.csv") | ||
| p = os.path.join(WORKDIR, f"{_safe_name(name)}_{uuid.uuid4().hex[:8]}.csv") | ||
| (obj.to_frame() if isinstance(obj, pd.Series) else pd.DataFrame(obj)).to_csv( | ||
| p, index=False | ||
| ) | ||
|
|
@@ -100,7 +130,7 @@ def write_series(obj, *, name: str = "result") -> str: | |
|
|
||
| def write_json(obj: dict, *, name: str = "result") -> str: | ||
| _ensure_workdir() | ||
| p = os.path.join(WORKDIR, f"{name}_{uuid.uuid4().hex[:8]}.json") | ||
| p = os.path.join(WORKDIR, f"{_safe_name(name)}_{uuid.uuid4().hex[:8]}.json") | ||
| json.dump(obj, open(p, "w"), indent=2, default=str) | ||
| return f"file://{p}" | ||
|
|
||
|
|
@@ -124,6 +154,6 @@ def materialize_iot( | |
| df.insert( | ||
| 0, "timestamp", pd.date_range("2020-01-01", periods=len(arr), freq=freq) | ||
| ) | ||
| p = os.path.join(WORKDIR, f"iot_{asset_id}.csv") | ||
| p = os.path.join(WORKDIR, f"iot_{_safe_name(asset_id)}.csv") | ||
| df.to_csv(p, index=False) | ||
| return f"file://{p}" | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Writes are only confined for
save_to._with_finetune(line 91) merges thefinetuneblock into the estimator params, and TinyTimeMixer/PatchTST passtraining_argsstraight totransformers.TrainingArguments. Sofinetune: {training_args: {output_dir: ...}}makes the Trainer write checkpoints, including the pickledtraining_args.bin, to any writable path.Suggest overriding
output_dirwith a server-chosen directory underWORKDIRand rejecting any other path-valued training args.