POST /v1/clean
POST https://api.sequenxai.com/v1/clean 是免费的清洗预演(Beta):运行与 /v1/forecast 相同的清洗阶段,只返回清洗报告和生效策略,不调用模型、不产生预测。用它在正式预测前确认数据会被怎样处理,并拿到可以传给 accept / decline 的决定 id。
1. 请求示例
# 12 hourly slots; 06:00 is missing. Free dry run: no model call, no points billed.
curl -sS -X POST "https://api.sequenxai.com/v1/clean" \
-H "Authorization: Bearer $SEQUENX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"freq": "1h",
"series": [
{"unique_id": "meter-001", "ds": "2026-09-01T00:00:00Z", "y": 142.5},
{"unique_id": "meter-001", "ds": "2026-09-01T01:00:00Z", "y": 141.8},
{"unique_id": "meter-001", "ds": "2026-09-01T02:00:00Z", "y": 140.9},
{"unique_id": "meter-001", "ds": "2026-09-01T03:00:00Z", "y": 139.7},
{"unique_id": "meter-001", "ds": "2026-09-01T04:00:00Z", "y": 139.2},
{"unique_id": "meter-001", "ds": "2026-09-01T05:00:00Z", "y": 138.8},
{"unique_id": "meter-001", "ds": "2026-09-01T07:00:00Z", "y": 138.2},
{"unique_id": "meter-001", "ds": "2026-09-01T08:00:00Z", "y": 139.0},
{"unique_id": "meter-001", "ds": "2026-09-01T09:00:00Z", "y": 140.4},
{"unique_id": "meter-001", "ds": "2026-09-01T10:00:00Z", "y": 141.3},
{"unique_id": "meter-001", "ds": "2026-09-01T11:00:00Z", "y": 142.0}
]
}'import math
import os
import requests
API_URL = "https://api.sequenxai.com/v1/clean"
HEADERS = {"Authorization": f"Bearer {os.environ['SEQUENX_API_KEY']}"}
# 48 hourly slots: 2026-09-01T20:00 is missing and one reading is the logger code -9999.
series = []
for i in range(48):
if i == 20:
continue
y = round(140 + 8 * math.sin(2 * math.pi * i / 24), 1)
series.append({
"unique_id": "meter-001",
"ds": f"2026-09-{1 + i // 24:02d}T{i % 24:02d}:00:00Z",
"y": -9999 if i == 30 else y,
})
def dry_run(options=None):
payload = {"series": series, "freq": "1h", "model": "hora-1.0"}
if options:
payload["options"] = options
resp = requests.post(API_URL, headers=HEADERS, json=payload, timeout=60)
body = resp.json()
if resp.status_code != 200:
error = body["error"]
raise SystemExit(f"{resp.status_code} {error['code']}: {error['message']} ({error['request_id']})")
return body
report = dry_run()["cleaning_report"]
for decision in report["decisions"]:
print(decision["id"], decision["class"], decision["status"])
# value.sentinel:meter-001:-9999 fabricate applied
# grid.regrid:meter-001 structural applied
# gap.impute:meter-001 fabricate applied
# -9999 is a real reading here: decline the decision by its id and look again.
report = dry_run({"cleaning": {"decline": ["value.sentinel:meter-001:-9999"]}})["cleaning_report"]
print([(d["id"], d["status"]) for d in report["decisions"]][0])
# ('value.sentinel:meter-001:-9999', 'declined')// Node.js 18+. Same 12-slot series as the curl example.
const hours = [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11];
const values = [142.5, 141.8, 140.9, 139.7, 139.2, 138.8, 138.2, 139.0, 140.4, 141.3, 142.0];
const series = hours.map((h, i) => ({
unique_id: "meter-001",
ds: `2026-09-01T${String(h).padStart(2, "0")}:00:00Z`,
y: values[i],
}));
const resp = await fetch("https://api.sequenxai.com/v1/clean", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SEQUENX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ series, freq: "1h" }),
});
const body = await resp.json();
if (!resp.ok) throw new Error(`${resp.status} ${body.error.code}: ${body.error.message}`);
console.log(body.effective_policy.id);
for (const d of body.cleaning_report.decisions) {
console.log(d.id, d.class, d.status, d.before, d.after);
}2. 请求体字段
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
series | array | 是 | — | 与 /v1/forecast 相同的长表记录,至少 1 行,单次最多 200,000 行。 |
freq | string | 否 | 推断 | 同 /v1/forecast。 |
clean_series | boolean | 否 | true | false 时只检测不修复,需要修复即返回 422。 |
model | string | 否 | — | 传入时检查该模型的最小长度(SERIES_TOO_SHORT),实体长于其最大上下文时与 /v1/forecast 一样返回 422 SERIES_TOO_LONG;不会截断历史。取值同 /v1/forecast。 |
options | object | 否 | — | 与 /v1/forecast 相同:timezone、ds_format、gap_split_min_steps、cleaning.preset / version / accept / decline,以及 2026-11 的调节项;options.cleaning 内未列出的字段返回 422 VALIDATION_ERROR。可以用它免费预览策略版本 2026-11。 |
没有 h、level、future_covariates 等预测字段;传入会因字段不在契约内返回 422 VALIDATION_ERROR。
3. 限制与计费
| 项目 | 规则 |
|---|---|
| 计费 | 免费。每次调用记一条 0 计费点的用量记录。 |
| 每日点数额度 | 不占用。 |
| 限流 | 每个账号每分钟 60 次(滑动窗口,账号下所有密钥共用),与 /v1/forecast 的限流分开计数;超出返回 429,X-RateLimit-Policy: clean-rpm。 |
| 共享容量 | 清洗容量所有账号共享,预演不排队:容量满时返回 503 SERVICE_UNAVAILABLE 并带 Retry-After,按该间隔重试即可。 |
| 单次行数 | 最多 200,000 行,超出返回 422 POINTS_EXCEEDED(超大请求直接拒绝,不排队);按规则网格展开后的点数过多时同样返回该错误码。 |
raw 预设 | 不接受:raw 不做清洗,没有报告可看,返回 422 CLEANING_OPTION_UNSUPPORTED(details.field 为 options.cleaning.preset)。 |
4. 响应字段
| 字段 | 说明 |
|---|---|
request_id | 服务端请求 ID,与响应头 X-Request-Id 相同。 |
freq | 传入 freq 时原样返回,省略时为推断出的规范写法。 |
cleaning_report | cleaning_report/1 结构化报告,结构见清洗策略与清洗报告。 |
effective_policy | {id, preset, version, source},与 /v1/forecast 相同。 |
5. 响应示例
下例是第 1 节 curl 请求的响应,series[] 明细已折叠:
{
"request_id": "req_9c2e7b1d4a8f4f3e9d6c5b4a3f2e1d0c",
"freq": "1h",
"cleaning_report": {
"schema_version": "cleaning_report/1",
"policy": {
"id": "default@2026-13",
"clean_series": true,
"duplicate_policy": "last",
"gap_fill_max_steps": 3,
"gap_split_min_steps": null,
"timezone_default": "UTC",
"accept": [],
"decline": [],
"...": "..."
},
"overall": {
"series_count": 1,
"input_points": 11,
"output_points": 12,
"health_score": 84,
"health_tier": "Healthy",
"forecastable": true
},
"series": ["... per-series input / freq / issues / actions / metrics / timezone ..."],
"warnings": [],
"blocking": [],
"decisions": [
{
"id": "grid.regrid:meter-001",
"rule": "grid.regrid",
"class": "structural",
"status": "applied",
"unique_id": "meter-001",
"action": "insert_empty_slots",
"before": {"slots": 11},
"after": {"slots": 12},
"reversible": false,
"evidence": {}
},
{
"id": "gap.impute:meter-001",
"rule": "gap.impute",
"class": "fabricate",
"status": "applied",
"unique_id": "meter-001",
"action": "pchip",
"before": {"missing": 1},
"after": {"filled": 1},
"reversible": false,
"evidence": {}
}
]
},
"effective_policy": {
"id": "default@2026-13",
"preset": "default",
"version": "2026-13",
"source": "platform"
}
}6. 清洗被拒时
数据无法在当前策略下安全预测时,/v1/clean 与 /v1/forecast 返回同样的 422 错误码,并在 error.details.cleaning_report 中附上完整报告。blocking[] 说明被拒的原因:
{
"error": {
"code": "SERIES_DISCONTINUOUS",
"message": "A gap ending shortly before the last observation is too long to fill reliably; the forecast would start from filled values. Send the missing recent values, or send only the history before the gap",
"request_id": "req_…",
"details": {
"cleaning_report": {
"schema_version": "cleaning_report/1",
"overall": {"forecastable": false, "...": "..."},
"blocking": [
{"code": "health_critical", "unique_id": "meter-001", "count": null, "missing_ratio": 0.119048,
"reason": "gap_near_origin_too_long", "reasons": ["gap_near_origin_too_long"],
"period": 24, "near_gap_steps": 20, "far_gap_steps": 0}
],
"decisions": ["..."]
}
}
}
}各错误码的处理方法见错误码与排障。
7. 与 /v1/forecast 的关系
- 同样的
series、freq、model与options下,两者的清洗结果与决定id一致,可以把预演里看到的id直接放进/v1/forecast的options.cleaning.accept/decline。 - 不传
model时预演既不检查最小长度,也不检查最大上下文;想看到与预测完全一致的报告(或SERIES_TOO_LONG拒绝),请传入同一个model。 - 预测还会做
h与每日额度等检查,这些不在预演范围内。