feat(workbench): expand SCADA monitoring and investigation presentation
This commit is contained in:
@@ -21,6 +21,22 @@ const eventSchema = z.object({
|
||||
detail: z.string(), task_id: z.string().nullable(), category: z.string().optional(),
|
||||
change: z.enum(["raised", "resolved"]).optional(), resolved_at: z.string().optional()
|
||||
});
|
||||
const monitoringWeightsSchema = z.object({
|
||||
burst_detection: z.number(), pressure: z.number(), outlet_flow: z.number()
|
||||
});
|
||||
const monitoringConfigSchema = z.object({
|
||||
rule_version: z.string(), decision_policy: z.enum(["weighted", "all_signals"]),
|
||||
pressure_device_ids: z.array(z.string()), outlet_flow_device_ids: z.array(z.string()),
|
||||
weights: monitoringWeightsSchema, alert_threshold: z.number()
|
||||
});
|
||||
const calibrationSchema = z.object({
|
||||
version: z.string().optional(), diagnostic_policy: z.string().optional(),
|
||||
status: z.enum(["ready", "insufficient_data"]),
|
||||
knowledge_cutoff: z.string(), history_start: z.string(),
|
||||
pressure: z.record(z.string(), z.object({ threshold_percent: z.number() })),
|
||||
outlet_flow: z.record(z.string(), z.object({ threshold_percent: z.number() })),
|
||||
burst_detection: z.object({ threshold: z.number().optional() }).optional()
|
||||
});
|
||||
const monitoringSchema = z.object({
|
||||
monitoring_id: z.string().uuid(),
|
||||
period_start: z.string(), period_end: z.string(), knowledge_cutoff: z.string(),
|
||||
@@ -29,7 +45,19 @@ const monitoringSchema = z.object({
|
||||
detail: z.string(), task_id: z.string().uuid().nullable(),
|
||||
pressure: z.record(z.string(), z.unknown()),
|
||||
outlet_flow: z.record(z.string(), z.unknown()),
|
||||
burst_detection: z.record(z.string(), z.unknown())
|
||||
burst_detection: z.record(z.string(), z.unknown()),
|
||||
anomaly_signals: z.array(z.object({
|
||||
event_id: z.string(), rule_id: z.string(), device_ids: z.array(z.string()), detail: z.string()
|
||||
})).optional(),
|
||||
weighted: z.object({
|
||||
score: z.number(), threshold: z.number(),
|
||||
weights: monitoringWeightsSchema
|
||||
}).passthrough().nullable().optional(),
|
||||
incident: z.object({
|
||||
incident_id: z.string().uuid(), status: z.enum(["active", "resolved"]),
|
||||
action: z.enum(["raised", "ongoing", "resolved"]),
|
||||
started_at: z.string(), last_observed_at: z.string(), recovery_count: z.number().int()
|
||||
}).nullable().optional()
|
||||
}).passthrough();
|
||||
export const scadaReplaySchema = z.object({
|
||||
replay_id: z.string().uuid(), project_id: z.string().uuid(), revision: z.number().int(),
|
||||
@@ -39,6 +67,8 @@ export const scadaReplaySchema = z.object({
|
||||
step_pending: z.boolean().default(false),
|
||||
monitoring_count: z.number().int().default(0),
|
||||
last_monitoring: monitoringSchema.nullable().default(null),
|
||||
monitoring_config: monitoringConfigSchema.nullable().optional(),
|
||||
monitoring_calibration: calibrationSchema.nullable().optional(),
|
||||
delivered_until: z.string().nullable(), processed_until: z.string().nullable(),
|
||||
processed_count: z.number(), anomaly_count: z.number(),
|
||||
devices: z.array(z.object({ device_id: z.string(), measurement_unit: z.string(), device_type: z.string() })),
|
||||
@@ -49,8 +79,11 @@ const capabilitiesSchema = z.object({
|
||||
available: z.boolean(), can_write: z.boolean(), reason: z.string().nullable(), current: scadaReplaySchema.nullable(),
|
||||
monitoring_interval_seconds: z.number().int().min(60).max(31_536_000).nullable().default(null),
|
||||
burst_monitoring_ready: z.boolean().default(false),
|
||||
burst_monitoring_reason: z.string().nullable().default(null)
|
||||
burst_monitoring_reason: z.string().nullable().default(null),
|
||||
monitoring_config: monitoringConfigSchema.nullable().optional()
|
||||
});
|
||||
export type ScadaMonitoringConfig = z.infer<typeof monitoringConfigSchema>;
|
||||
export type ScadaMonitoringCalibration = z.infer<typeof calibrationSchema>;
|
||||
export type ScadaReplay = z.infer<typeof scadaReplaySchema>;
|
||||
export type ScadaReplayRule = z.infer<typeof ruleSchema>;
|
||||
export type ScadaReplayEvent = z.infer<typeof eventSchema>;
|
||||
@@ -85,6 +118,12 @@ export class ScadaReplayClient {
|
||||
request_id: crypto.randomUUID(), expected_revision: replay.revision, action
|
||||
}));
|
||||
}
|
||||
async monitorAt(replay: ScadaReplay, atTime: string) {
|
||||
return scadaReplaySchema.parse(await this.request(`/${replay.replay_id}/commands`, {
|
||||
request_id: crypto.randomUUID(), expected_revision: replay.revision,
|
||||
action: "monitor", at_time: atTime
|
||||
}));
|
||||
}
|
||||
private async request(path: string, body?: unknown, signal?: AbortSignal) {
|
||||
this.projectId ??= findOwnedWorkbenchProject(this.access).then(({ project }) => project.project_id)
|
||||
.catch((error) => { this.projectId = null; throw error; });
|
||||
|
||||
Reference in New Issue
Block a user