Files
next-tjwater-frontend/tests/browser/peripheral-sample-panel.e2e.ts
T

548 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
test.describe("外围样板管理", () => {
test.beforeEach(async ({ page }) => {
await mockAgentApi(page);
await page.route("**/runtime-config.js", (route) =>
route.fulfill({
contentType: "text/javascript",
body: `globalThis.__TJWATER_CONFIG__ = ${JSON.stringify({
TJWATER_AUTH_MODE: "disabled",
TJWATER_SERVER_API_BASE_URL: "http://server.test",
TJWATER_MAP_URL: "http://map.test/geoserver",
TJWATER_GEOSERVER_WORKSPACE: "tjwater_next"
})};`
})
);
await page.route("http://server.test/api/v1/projects**", (route) =>
route.fulfill({
json: {
items: [
{
project_id: "324ff7ee-c2d4-5184-a84b-48fb14a2933e",
gs_workspace: "tjwater_next",
status: "active"
}
],
total: 1,
limit: 1000,
offset: 0
}
})
);
await page.route("http://server.test/api/v1/scada-devices**", (route) =>
route.fulfill({ json: { items: [], total: 0, limit: 1000, offset: 0 } })
);
await page.route("http://map.test/**", (route) => route.fulfill({ status: 204 }));
});
test("展示已配置的历史回放与合成样本", async ({ page }, testInfo) => {
const observationQueries: Record<string, string>[] = [];
const writeRequests: string[] = [];
await page.route("**/runtime-config.js", (route) =>
route.fulfill({
contentType: "text/javascript",
body: `globalThis.__TJWATER_CONFIG__ = ${JSON.stringify({
TJWATER_AUTH_MODE: "disabled",
TJWATER_SERVER_API_BASE_URL: "http://server.test",
TJWATER_PERIPHERAL_SAMPLE_API_BASE_URL: "http://peripheral.test",
TJWATER_PERIPHERAL_SAMPLE_TIME_MODE: "demo",
TJWATER_PERIPHERAL_SAMPLE_DEMO_START: "2026-04-30T08:00:00Z",
TJWATER_PERIPHERAL_SAMPLE_DEMO_END: "2026-04-30T16:00:00Z"
})};`
})
);
await page.route("http://server.test/api/v1/projects**", (route) =>
route.fulfill({
json: {
items: [
{
project_id: "324ff7ee-c2d4-5184-a84b-48fb14a2933e",
gs_workspace: "tjwater_next",
status: "active"
}
],
total: 1,
limit: 1000,
offset: 0
}
})
);
const feedback = {
feedback_id: "40000000-0000-4000-8000-000000000001",
task_id: "10000000-0000-4000-8000-000000000001",
recorded_by: "operator",
recorded_at: "2026-04-30T09:10:00Z",
occurred_at: "2026-04-30T09:00:00Z",
applicable_time: { start: "2026-04-30T08:00:00Z", end: "2026-04-30T16:00:00Z" },
asset_ids: ["DEMO-P001"],
finding: "instrument_fault",
notes: "压力变送器零点漂移",
attachment_refs: [],
origin: "synthetic"
};
const task = {
task_id: feedback.task_id,
project_id: "324ff7ee-c2d4-5184-a84b-48fb14a2933e",
revision: 3,
title: "DEMO-P001 测点偏差核查",
summary: "校验记录外围核查",
source: {
system: "next",
task_id: "20000000-0000-4000-8000-000000000001",
object_type: "validation",
object_id: "30000000-0000-4000-8000-000000000001",
revision: 2
},
asset_ids: ["DEMO-P001"],
analysis_window: { start: "2026-04-30T08:00:00Z", end: "2026-04-30T16:00:00Z" },
evidence_refs: [],
requested_check: "核查仪表状态",
status: "pending_review",
origin: "synthetic",
created_at: "2026-04-30T08:05:00Z",
updated_at: "2026-04-30T09:10:00Z",
created_by: "operator",
history: [
{
sequence: 1,
from_status: null,
to_status: "pending",
actor: "operator",
at: "2026-04-30T08:05:00Z",
reason: "提交样板核查"
},
{
sequence: 2,
from_status: "processing",
to_status: "pending_review",
actor: "operator",
at: "2026-04-30T09:10:00Z",
reason: "等待审核"
}
],
feedback: [feedback]
};
await page.route("http://peripheral.test/api/sample/v1/**", async (route) => {
const url = new URL(route.request().url());
if (route.request().method() !== "GET") writeRequests.push(route.request().url());
if (url.pathname.endsWith("/monitoring/observations"))
observationQueries.push(Object.fromEntries(url.searchParams));
if (url.pathname.endsWith("/context"))
return route.fulfill({
json: {
protocol_version: "tjwater-peripheral-sample@1",
origin: "synthetic",
project_id: task.project_id,
user_id: "50000000-0000-4000-8000-000000000001",
can_write: true,
scenarios: ["normal"],
managed_resources: { work_orders: "available", test_data: "available" },
state_transitions: {
pending: ["processing"],
processing: ["pending_review"],
pending_review: ["processing", "completed"],
completed: []
}
}
});
if (url.pathname.endsWith("/scenario"))
return route.fulfill({ json: { scenario: "normal", revision: 1 } });
if (url.pathname.endsWith("/tasks"))
return route.fulfill({ json: { items: [task], next_cursor: null } });
if (url.pathname.endsWith("/work-orders"))
return route.fulfill({ json: { items: createSourceWorkOrders(task.project_id), next_cursor: null } });
if (url.pathname.endsWith("/test-data"))
return route.fulfill({ json: { items: [], next_cursor: null } });
if (url.pathname.endsWith("/monitoring/points"))
return route.fulfill({
json: {
items: Array.from({ length: 118 }, (_, index) => ({
point_id: `SCADA-${index + 1}`,
label: `压力测点 ${index + 1}`,
unit: "m",
status: "active",
origin: "historical_replay"
})),
next_cursor: null,
origin: "historical_replay",
source_id: "tjwater_v2"
}
});
if (url.pathname.endsWith("/monitoring/alerts"))
return route.fulfill({
json: {
availability: "not_configured",
items: [],
origin: "historical_replay",
reason: "来源未配置",
source_id: "tjwater_v2"
}
});
if (url.pathname.endsWith("/monitoring/observations"))
return route.fulfill({
json: {
origin: "historical_replay",
source_id: "tjwater_v2",
source_revision: "2026-04-30",
truncated: false,
items: [
{
record_id: "reading-1",
point_id: "SCADA-1",
observed_at: "2026-04-30T08:15:00Z",
value: 31.42,
quality: "valid",
unit: "m"
},
{
record_id: "reading-2",
point_id: "SCADA-1",
observed_at: "2026-04-30T08:30:00Z",
value: null,
quality: "missing",
unit: "m"
}
]
}
});
if (url.pathname.endsWith("/demo-data"))
return route.fulfill({ json: createDemoDataset(task.project_id) });
return route.fulfill({
status: 404,
json: { code: "NOT_FOUND", message: "not found", effect: "none", request_id: null }
});
});
await page.goto("/");
await page.getByRole("button", { name: "外围样板管理" }).click();
await expect(page.getByText(/118 个测点 · 告警来源未配置 · 真实项目样本回放/)).toBeVisible();
const hierarchy = page.getByLabel("任务层级");
await expect(hierarchy).toContainText("外围合成数据核查");
await expect(hierarchy).toContainText("3 个子任务");
await expect(page.getByLabel("子任务阶段 合成通信核查")).toContainText(/派发.*当前阶段.*执行.*待开始.*复令.*待开始/);
await expect(page.getByLabel("子任务阶段 合成压力偏差复核")).toContainText(/派发.*已完成.*执行.*当前阶段.*复令.*待开始/);
await expect(page.getByLabel("子任务阶段 合成零流量样本确认")).toContainText(/派发.*已完成.*执行.*已完成.*复令.*已完成/);
const scenarioSelect = page.getByRole("combobox", { name: "场景" });
await scenarioSelect.focus();
await page.keyboard.press("Enter");
await expect(page.getByRole("listbox")).toBeVisible();
await page.keyboard.press("Escape");
await expect(page.getByRole("listbox")).toBeHidden();
await expect(scenarioSelect).toBeFocused();
const pointSelect = page.getByRole("combobox", { name: "测点", exact: true });
await pointSelect.click();
await page.getByRole("option", { name: "压力测点 2 · m" }).click();
await expect(pointSelect).toContainText("压力测点 2 · m");
const historyMode = page.getByRole("combobox", { name: "时间模式", exact: true });
const historyStart = page.getByRole("button", { name: "开始(UTC)", exact: true });
const historyEnd = page.getByRole("button", { name: "结束(UTC)", exact: true });
await expect(historyStart).toContainText("2026-04-30 08:00");
await historyMode.click();
await page.getByRole("option", { name: "最近 8 小时", exact: true }).click();
await expect(page.getByText(/UTC 历史窗口 · 最近 8 小时/)).toBeVisible();
await expect(historyStart).not.toContainText("2026-04-30 08:00");
await historyMode.click();
await page.getByRole("option", { name: "演示历史窗口", exact: true }).click();
await expect(historyStart).toContainText("2026-04-30 08:00");
await expect(historyEnd).toContainText("2026-04-30 16:00");
await historyStart.click();
const startDialog = page.getByRole("dialog", { name: "选择开始(UTC)" });
await startDialog.getByRole("gridcell", { name: "2026年4月29日", exact: true }).click();
await startDialog.getByRole("combobox", { name: "分钟" }).click();
await page.getByRole("option", { name: "15", exact: true }).click();
await startDialog.getByRole("button", { name: "确认", exact: true }).click();
await expect(historyStart).toContainText("2026-04-29 08:15");
await historyEnd.click();
const endDialog = page.getByRole("dialog", { name: "选择结束(UTC)" });
await endDialog.getByRole("combobox", { name: "分钟" }).click();
await page.getByRole("option", { name: "45", exact: true }).click();
await endDialog.getByRole("button", { name: "确认", exact: true }).click();
await expect(historyEnd).toContainText("2026-04-30 16:45");
expect(observationQueries).toEqual([]);
await page.getByRole("button", { name: "读取历史读数" }).click();
await expect(page.getByText("31.42 m")).toBeVisible();
await expect(page.getByText("缺失")).toBeVisible();
expect(observationQueries).toEqual([
{ point_id: "SCADA-2", start: "2026-04-29T08:15:00Z", end: "2026-04-30T16:45:00Z" }
]);
await page.getByText("合成演示样本", { exact: true }).click();
await page.getByRole("button", { name: "手动读取合成样本" }).click();
await expect(
page.getByText(/5 个虚拟测点,240 条读数,9 条状态,2 条告警,4 条运行事件/)
).toBeVisible();
await expect(page.getByText("0 m", { exact: true }).first()).toBeVisible();
await expect(page.getByText("缺失", { exact: true }).first()).toBeVisible();
for (const width of [320, 375, 1280]) {
await page.setViewportSize({ width, height: 720 });
await pointSelect.scrollIntoViewIfNeeded();
await pointSelect.click();
await expect(page.getByRole("listbox")).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(
width
);
await testInfo.attach(`peripheral-select-${width}px`, {
body: await page.screenshot(),
contentType: "image/png"
});
await page.keyboard.press("Escape");
await historyStart.click();
await expect(startDialog).toBeVisible();
await expect(
startDialog.locator(
'input[type="datetime-local"], input[type="date"], input[type="time"], select'
)
).toHaveCount(0);
await startDialog.getByRole("gridcell", { name: "2026年4月28日", exact: true }).click();
const calendarBox = await startDialog.boundingBox();
expect(calendarBox!.x).toBeGreaterThanOrEqual(0);
expect(calendarBox!.x + calendarBox!.width).toBeLessThanOrEqual(width);
expect(
await startDialog.evaluate((element) => element.scrollWidth <= element.clientWidth)
).toBe(true);
await page.screenshot({ path: `test-results/peripheral-datetime/${width}-calendar.png` });
await page.keyboard.press("Escape");
await expect(startDialog).toBeHidden();
await expect(historyStart).toBeFocused();
await expect(historyStart).toContainText("2026-04-29 08:15");
}
expect(observationQueries).toHaveLength(1);
expect(writeRequests).toEqual([]);
});
test("未配置时从顶栏进入且不暗连外围服务", async ({ page }) => {
const peripheralRequests: string[] = [];
page.on("request", (request) => {
if (request.url().includes("/api/sample/v1")) peripheralRequests.push(request.url());
});
await page.goto("/");
await page.getByRole("button", { name: "外围样板管理" }).click();
await expect(page.getByRole("complementary", { name: "外围样板管理" })).toBeVisible();
await expect(
page
.getByRole("complementary", { name: "外围样板管理" })
.getByText("外围系统模拟接入,不是正式工单/现场结果", { exact: true })
.last()
).toBeVisible();
await expect(page.getByText(/当前不会连接任何外围服务/)).toBeVisible();
expect(peripheralRequests).toEqual([]);
});
test("增量存储未供给时保留原有外围能力且不请求缺表资源", async ({ page }) => {
const projectId = "324ff7ee-c2d4-5184-a84b-48fb14a2933e";
const managedRequests: string[] = [];
await page.route("**/runtime-config.js", (route) => route.fulfill({
contentType: "text/javascript",
body: `globalThis.__TJWATER_CONFIG__=${JSON.stringify({
TJWATER_AUTH_MODE: "disabled",
TJWATER_SERVER_API_BASE_URL: "http://server.test",
TJWATER_PERIPHERAL_SAMPLE_API_BASE_URL: "http://peripheral.test"
})}`
}));
await page.route("http://peripheral.test/api/sample/v1/**", (route) => {
const path = new URL(route.request().url()).pathname;
if (path.includes("/work-orders") || path.includes("/test-data")) {
managedRequests.push(path);
return route.fulfill({ status: 503, json: {} });
}
if (path.endsWith("/context")) return route.fulfill({ json: {
protocol_version: "tjwater-peripheral-sample@1", origin: "synthetic", project_id: projectId,
user_id: "50000000-0000-4000-8000-000000000001", can_write: true,
scenarios: ["normal"], managed_resources: {
work_orders: "not_configured", test_data: "not_configured"
}, state_transitions: { pending: [], processing: [], pending_review: [], completed: [] }
} });
if (path.endsWith("/scenario")) return route.fulfill({ json: { scenario: "normal", revision: 1 } });
if (path.endsWith("/tasks")) return route.fulfill({ json: { items: [], next_cursor: null } });
if (path.endsWith("/monitoring/points")) return route.fulfill({ json: {
items: [], next_cursor: null, origin: "historical_replay", source_id: "tjwater_v2"
} });
if (path.endsWith("/monitoring/alerts")) return route.fulfill({ json: {
items: [], availability: "not_configured", reason: "告警源未配置",
origin: "historical_replay", source_id: "tjwater_v2"
} });
return route.fulfill({ status: 404, json: {} });
});
await page.goto("/");
await page.getByRole("button", { name: "外围样板管理" }).click();
const panel = page.getByRole("complementary", { name: "外围样板管理" });
await expect(panel.getByText("测试工单存储待供给", { exact: true })).toBeVisible();
await expect(panel.getByText("测试数据存储待供给", { exact: true })).toBeVisible();
await expect(panel.getByText("连接与演练场景", { exact: true })).toBeVisible();
expect(managedRequests).toEqual([]);
});
test("创建、修改并软删除测试工单和测试数据", async ({ page }) => {
const projectId = "324ff7ee-c2d4-5184-a84b-48fb14a2933e";
let workOrders: Record<string, unknown>[] = [];
let testData: Record<string, unknown>[] = [];
await page.route("**/runtime-config.js", (route) => route.fulfill({
contentType: "text/javascript",
body: `globalThis.__TJWATER_CONFIG__=${JSON.stringify({ TJWATER_AUTH_MODE: "disabled", TJWATER_SERVER_API_BASE_URL: "http://server.test", TJWATER_PERIPHERAL_SAMPLE_API_BASE_URL: "http://peripheral.test" })}`
}));
await page.route("http://peripheral.test/api/sample/v1/**", async (route) => {
const request = route.request();
const url = new URL(request.url());
const path = url.pathname;
const now = "2026-09-18T00:00:00Z";
if (path.endsWith("/context")) return route.fulfill({ json: { protocol_version: "tjwater-peripheral-sample@1", origin: "synthetic", project_id: projectId, user_id: "50000000-0000-4000-8000-000000000001", can_write: true, scenarios: ["normal"], managed_resources: { work_orders: "available", test_data: "available" }, state_transitions: { pending: ["processing"], processing: ["pending_review"], pending_review: ["processing", "completed"], completed: [] } } });
if (path.endsWith("/scenario")) return route.fulfill({ json: { scenario: "normal", revision: 1 } });
if (path.endsWith("/tasks")) return route.fulfill({ json: { items: [], next_cursor: null } });
if (path.endsWith("/monitoring/points")) return route.fulfill({ json: { items: [], next_cursor: null, origin: "historical_replay", source_id: "test" } });
if (path.endsWith("/monitoring/alerts")) return route.fulfill({ json: { items: [], availability: "not_configured", reason: "未配置", origin: "historical_replay", source_id: "test" } });
if (path.endsWith("/demo-data")) return route.fulfill({ json: createDemoDataset(projectId) });
if (path.endsWith("/work-orders") && request.method() === "GET") return route.fulfill({ json: { items: workOrders, next_cursor: null } });
if (path.endsWith("/work-orders") && request.method() === "POST") {
const body = request.postDataJSON();
const item = { ...body, work_order_id: "50000000-0000-4000-8000-000000000001", project_id: projectId, revision: 1, origin: "synthetic", created_at: now, updated_at: now, created_by: "tester", updated_by: "tester" };
delete item.request_id; workOrders = [item]; return route.fulfill({ status: 201, json: item });
}
if (path.includes("/work-orders/") && request.method() === "PATCH") {
const body = request.postDataJSON(); workOrders = [{ ...workOrders[0], ...body, revision: 2 }]; delete workOrders[0].request_id; delete workOrders[0].expected_revision;
return route.fulfill({ json: workOrders[0] });
}
if (path.includes("/work-orders/") && request.method() === "DELETE") { workOrders = []; return route.fulfill({ json: { resource_type: "work_order", resource_id: path.split("/").at(-1), revision: 3, deleted: true } }); }
if (path.endsWith("/test-data") && request.method() === "GET") return route.fulfill({ json: { items: testData, next_cursor: null } });
if (path.endsWith("/test-data") && request.method() === "POST") {
const body = request.postDataJSON(); const item = { ...body, record_id: "60000000-0000-4000-8000-000000000001", project_id: projectId, revision: 1, origin: "synthetic", created_at: now, updated_at: now, created_by: "tester", updated_by: "tester" };
delete item.request_id; testData = [item]; return route.fulfill({ status: 201, json: item });
}
if (path.includes("/test-data/") && request.method() === "PATCH") { const body = request.postDataJSON(); testData = [{ ...testData[0], ...body, revision: 2 }]; delete testData[0].request_id; delete testData[0].expected_revision; return route.fulfill({ json: testData[0] }); }
if (path.includes("/test-data/") && request.method() === "DELETE") { testData = []; return route.fulfill({ json: { resource_type: "test_data_record", resource_id: path.split("/").at(-1), revision: 3, deleted: true } }); }
return route.fulfill({ status: 404, json: { code: "NOT_FOUND", message: "not found", effect: "none", request_id: null } });
});
await page.goto("/");
await page.getByRole("button", { name: "外围样板管理" }).click();
const panel = page.getByRole("complementary", { name: "外围样板管理" });
await expect(panel.getByText("外围任务编排", { exact: true })).toBeVisible();
await expect(panel.getByLabel("任务层级")).toContainText("外围合成数据核查");
await expect(panel.getByRole("textbox", { name: "标题" })).toBeHidden();
await panel.getByRole("button", { name: "新增子任务" }).click();
await panel.getByRole("textbox", { name: "标题" }).fill("演示 测试工单");
await panel.getByRole("textbox", { name: "说明" }).fill("外围 CRUD 联调");
await panel.getByRole("button", { name: "创建子任务" }).click();
await expect(panel.getByText("演示 测试工单", { exact: true })).toBeVisible();
await panel.getByRole("button", { name: "编辑子任务 演示 测试工单" }).click();
await panel.getByRole("textbox", { name: "标题" }).fill("演示 测试工单(已改)");
await panel.getByRole("button", { name: "保存修改" }).click();
await expect(panel.getByText("演示 测试工单(已改)", { exact: true })).toBeVisible();
await panel.getByRole("button", { name: "删除子任务 演示 测试工单(已改)" }).click();
await expect(panel.getByText("演示 测试工单(已改)", { exact: true })).toBeHidden();
await panel.getByRole("group", { name: "外围样板资源类型" }).getByRole("button", { name: /测试数据/ }).click();
await panel.getByRole("button", { name: "新增测试数据" }).click();
await panel.getByRole("textbox", { name: "名称" }).fill("演示 压力样本");
await panel.getByRole("textbox", { name: "数值" }).fill("31.2");
await panel.getByRole("button", { name: "创建数据" }).click();
await expect(panel.getByText("演示 压力样本", { exact: true })).toBeVisible();
await panel.getByRole("button", { name: "编辑数据 演示 压力样本" }).click();
await panel.getByRole("textbox", { name: "数值" }).fill("30.8");
await panel.getByRole("button", { name: "保存修改" }).click();
await expect(panel.getByText(/30.8 m/)).toBeVisible();
await panel.getByRole("button", { name: "删除数据 演示 压力样本" }).click();
await expect(panel.getByText("演示 压力样本", { exact: true })).toBeHidden();
});
test("在 375px 视口内完整呈现面板", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 720 });
await page.goto("/");
const launcher = page.getByRole("button", { name: "外围样板管理" });
await launcher.focus();
await page.keyboard.press("Enter");
const panel = page.getByRole("complementary", { name: "外围样板管理" });
await expect(panel).toBeVisible();
const box = await panel.boundingBox();
expect(box).not.toBeNull();
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(375);
});
});
function createDemoDataset(projectId: string) {
const points = Array.from({ length: 5 }, (_, index) => ({
point_id: `DEMO-${index + 1}`,
label: `虚拟压力测点 ${index + 1}`,
unit: "m",
status: "active",
origin: "synthetic"
}));
return {
generator_version: "water-demo@1",
seed: "water-demo-seed-20260917",
origin: "synthetic",
window: { start: "2026-09-17T00:00:00Z", end: "2026-09-17T12:00:00Z" },
project_id: projectId,
source_id: "peripheral-demo",
source_revision: "demo-r1",
points,
observations: Array.from({ length: 240 }, (_, index) => ({
record_id: `demo-reading-${index}`,
point_id: `DEMO-${(index % 5) + 1}`,
observed_at: `2026-09-17T${String(Math.floor(index / 20)).padStart(2, "0")}:${String((index % 4) * 15).padStart(2, "0")}:00Z`,
value: index === 0 ? 0 : index === 5 ? null : 31 + index / 100,
quality: index === 5 ? "missing" : "valid",
unit: "m"
})),
device_states: Array.from({ length: 9 }, (_, index) => ({
record_id: `60000000-0000-4000-8000-${String(index).padStart(12, "0")}`,
asset_id: `VIRTUAL-${index + 1}`,
asset_type: "pressure_sensor",
observed_at: "2026-09-17T01:00:00Z",
state: index === 1 ? "offline" : "online"
})),
alerts: [
{
alert_id: "70000000-0000-4000-8000-000000000001",
asset_id: "VIRTUAL-2",
code: "communication_lost",
message: "演示通信中断",
occurred_at: "2026-09-17T02:00:00Z",
cleared_at: "2026-09-17T02:15:00Z"
},
{
alert_id: "70000000-0000-4000-8000-000000000002",
asset_id: "VIRTUAL-3",
code: "low_level",
message: "演示低水位",
occurred_at: "2026-09-17T05:00:00Z",
cleared_at: null
}
],
operations: Array.from({ length: 4 }, (_, index) => ({
record_id: `80000000-0000-4000-8000-${String(index).padStart(12, "0")}`,
actor: "synthetic-generator",
asset_id: `VIRTUAL-${index + 1}`,
operation: ["pump_start", "pump_stop", "valve_open", "valve_close"][index],
note: "演示事件,不是控制指令",
occurred_at: `2026-09-17T0${index + 1}:30:00Z`
}))
};
}
function createSourceWorkOrders(projectId: string) {
return [
["合成通信核查", "核查监测点通信和数据上送状态", "open", "high"],
["合成压力偏差复核", "复核压力偏差与历史回放证据", "in_progress", "urgent"],
["合成零流量样本确认", "确认零流量样本及质量标记", "completed", "normal"]
].map(([title, summary, status, priority], index) => ({
work_order_id: `50000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`,
project_id: projectId,
revision: 1,
title,
summary,
status,
priority,
assignee: null,
scheduled_at: "2026-09-17T08:00:00Z",
origin: "synthetic",
created_at: "2026-09-17T00:00:00Z",
updated_at: "2026-09-17T00:00:00Z",
created_by: "synthetic-generator",
updated_by: "synthetic-generator"
}));
}