feat(workbench): add runtime GIS and sample integrations
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""Pure, versioned generator. No clock, random process state, network or writes."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from hashlib import sha256
|
||||
from uuid import NAMESPACE_URL, uuid5
|
||||
|
||||
from .schemas import DemoContent
|
||||
|
||||
VERSION = "water-demo@2"
|
||||
DEFAULT_SEED = "tjwater-peripheral-demo-20260917"
|
||||
DEFAULT_START = datetime(2026, 9, 17, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def build_demo_content(seed: str = DEFAULT_SEED, start: datetime = DEFAULT_START) -> dict:
|
||||
"""Same version/seed/UTC start produce identical canonical content and IDs."""
|
||||
if not isinstance(seed, str) or not 1 <= len(seed) <= 128:
|
||||
raise ValueError("seed must contain 1 to 128 characters")
|
||||
if not isinstance(start, datetime) or start.tzinfo is None or start.utcoffset() is None:
|
||||
raise ValueError("start must include timezone")
|
||||
start = start.astimezone(timezone.utc)
|
||||
if start.second or start.microsecond or start.minute % 15:
|
||||
raise ValueError("start must be aligned to a 15-minute UTC boundary")
|
||||
prefix = f"{VERSION}/{seed}/{start.isoformat()}"
|
||||
|
||||
def record_id(kind: str, key: str) -> str:
|
||||
return str(uuid5(NAMESPACE_URL, f"{prefix}/{kind}/{key}"))
|
||||
|
||||
def at(index: int) -> str:
|
||||
return (start + timedelta(minutes=15 * index)).isoformat()
|
||||
|
||||
points = [
|
||||
("DEMO-P001", "演示压力测点 1", "m", 24.37),
|
||||
("DEMO-P002", "演示压力测点 2", "m", 31.26),
|
||||
("DEMO-P003", "演示压力测点 3", "m", 31.81),
|
||||
("DEMO-F001", "演示出水流量", "m3/h", 186.43),
|
||||
("DEMO-L001", "演示水池液位", "m", 4.26),
|
||||
]
|
||||
observations = []
|
||||
for index in range(48):
|
||||
for point_id, _label, unit, base in points:
|
||||
noise = int(sha256(f"{prefix}/{point_id}/{index}".encode()).hexdigest()[:8], 16) % 101
|
||||
value = round(base + (noise - 50) / 100, 2)
|
||||
quality = "valid"
|
||||
if point_id == "DEMO-P001" and 12 <= index < 16:
|
||||
value, quality = None, "missing"
|
||||
elif point_id == "DEMO-F001" and 24 <= index < 28:
|
||||
value = 0.0
|
||||
elif point_id == "DEMO-L001" and 36 <= index < 40:
|
||||
value = round(1.35 + noise / 1000, 3)
|
||||
observations.append({"record_id": record_id("observation", f"{point_id}/{index}"),
|
||||
"point_id": point_id, "observed_at": at(index), "value": value,
|
||||
"quality": quality, "unit": unit})
|
||||
states = []
|
||||
for asset, kind, changes in (
|
||||
("DEMO-P001", "pressure_sensor", [(0, "online"), (12, "offline"), (16, "online")]),
|
||||
("DEMO-PUMP-01", "pump", [(0, "running"), (24, "stopped"), (28, "running")]),
|
||||
("DEMO-VALVE-01", "valve", [(0, "open"), (24, "closed"), (28, "open")]),
|
||||
):
|
||||
for index, state in changes:
|
||||
states.append({"record_id": record_id("state", f"{asset}/{index}"), "asset_id": asset,
|
||||
"asset_type": kind, "observed_at": at(index), "state": state})
|
||||
states.sort(key=lambda record: (record["observed_at"], record["asset_id"]))
|
||||
alerts = [
|
||||
{"alert_id": record_id("alert", "communication"), "asset_id": "DEMO-P001",
|
||||
"occurred_at": at(12), "cleared_at": at(16), "code": "communication_lost",
|
||||
"message": "预置通信中断告警样例,仅为合成情节,不是现场诊断"},
|
||||
{"alert_id": record_id("alert", "level"), "asset_id": "DEMO-L001",
|
||||
"occurred_at": at(36), "cleared_at": at(40), "code": "low_level",
|
||||
"message": "预置低液位告警样例,不采用生产阈值或专业告警算法"},
|
||||
]
|
||||
operations = [
|
||||
{"record_id": record_id("operation", operation), "asset_id": asset, "occurred_at": at(index),
|
||||
"operation": operation, "actor": "synthetic-generator", "note": "合成运行历史,不是设备指令或实际操作回执"}
|
||||
for asset, index, operation in (("DEMO-PUMP-01", 24, "pump_stop"),
|
||||
("DEMO-VALVE-01", 24, "valve_close"), ("DEMO-PUMP-01", 28, "pump_start"),
|
||||
("DEMO-VALVE-01", 28, "valve_open"))
|
||||
]
|
||||
content = {"generator_version": VERSION, "seed": seed, "origin": "synthetic",
|
||||
"window": {"start": at(0), "end": at(48)},
|
||||
"points": [{"point_id": point, "label": label, "unit": unit, "status": "unknown", "origin": "synthetic"}
|
||||
for point, label, unit, _ in points],
|
||||
"observations": observations, "device_states": states, "alerts": alerts, "operations": operations}
|
||||
return DemoContent.model_validate(content).model_dump(mode="json")
|
||||
Reference in New Issue
Block a user