跳到主内容

可视化与交互增强

核心能力

可视化与交互增强

EnerOS v0.46.0 大幅提升可视化与交互能力,新增 3D 场景 glTF 2.0 导出、自然语言查询引擎(8 意图识别)与 PDF/Excel/Word 报表导出。运维人员无需学习复杂查询语言,即可通过自然语言与电网数据交互。可视化能力由 eneros-viz crate 提供。

可视化架构

┌──────────────────────────────────────────────────────────────┐
│                     交互层                                    │
│  自然语言查询 / 仪表盘 / Web 界面 / 移动端                   │
└────────┬─────────────────────────────────────────────────────┘

┌────────▼─────────────────────────────────────────────────────┐
│                     渲染层                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │
│  │ 3D 场景  │  │ 拓扑图   │  │ 图表     │  │ 报表     │    │
│  │ glTF 2.0 │  │ SVG/Canvas│  │ ECharts │  │ PDF/XLSX│    │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘    │
└────────┬─────────────────────────────────────────────────────┘

┌────────▼─────────────────────────────────────────────────────┐
│                     数据层                                    │
│  时序数据库 / 拓扑模型 / 告警 / Agent 状态                   │
└──────────────────────────────────────────────────────────────┘
模块输出格式适用
3D 场景glTF 2.0 / GLB站内巡视、设备巡视
自然语言查询JSON / 文本运维交互
报表导出PDF / Excel / Word报告归档
仪表盘HTML / WebSocket实时监控
拓扑图SVG / Canvas电网结构
图表PNG / SVG趋势分析

3D 场景

将电网拓扑与设备状态渲染为 3D 场景,支持 glTF 2.0 标准导出,可在 Three.js / Babylon.js / Cesium 等渲染引擎中加载:

use eneros_viz::scene::{Scene, SceneBuilder, GltfExporter, SubstationMesh, LineMesh, ColorScheme};

let mut scene = SceneBuilder::new()
    .scale(Scale::kilometers())
    .terrain(Terrain::from_dem("dem/srtm.tif"));

// 添加变电站
scene.add_substation(SubstationMesh::new("sub_a")
    .position(31.23, 121.47, 0.0)
    .voltage_level(220.0)
    .equipment(EquipmentMesh::all()));

scene.add_substation(SubstationMesh::new("sub_b")
    .position(31.25, 121.50, 0.0)
    .voltage_level(220.0));

// 添加线路
scene.add_line(LineMesh::between("sub_a", "sub_b")
    .voltage(220.0)
    .load_ratio(0.65)
    .conductor(ConductorType::Lgj400)
    .towers(TowerPlacement::every(500.0)));

// 添加设备状态
scene.add_device(DeviceMesh::transformer("t1")
    .position_in("sub_a")
    .load(0.78)
    .temperature(65.0));

// 状态着色
scene.color_by(ColorScheme::VoltageBand);  // 按电压着色

// 添加标注
scene.annotation(Annotation::label("sub_a", "220kV 变电站 A"));

// 导出 glTF
let gltf = GltfExporter::export(&scene.build())
    .embed_textures(true)
    .draco_compression(true)
    .version(GltfVersion::V2_0)?;
std::fs::write("grid.gltf", gltf)?;

场景层级

Scene
├── Substations
│   ├── Bays
│   │   ├── Breakers
│   │   ├── Disconnectors
│   │   └── Transformers
│   └── Control House
├── Lines
│   ├── Towers
│   └── Conductors
├── Generators
├── Loads
└── Annotations

着色方案

方案说明颜色映射
VoltageBand按电压等级红 (>1.05) / 绿 (0.95-1.05) / 蓝 (<0.95)
LoadRatio按负载率红 (>90%) / 黄 (70-90%) / 绿 (<70%)
DeviceStatus按设备状态红 (故障) / 黄 (告警) / 绿 (正常)
OutageArea停电区域灰 (失电) / 彩色 (正常)
RealtimeFlow实时潮流流向动画

自然语言查询

8 类意图识别,无需学习查询语法,支持中文自然语言:

use eneros_viz::nl::{NlQuery, Intent, NlResponse};

let response = NlQuery::ask("昨天上午 10 点 5 号母线电压是多少?").await?;

match response.intent {
    Intent::PointInTimeQuery => {
        println!("5 号母线 10:00 电压: {:.2} kV", response.value);
        println!("SQL 等价: {}", response.translated_sql);
    }
    Intent::RangeQuery => {
        println!("数据点数: {}", response.data_points.len());
    }
    _ => {}
}

// 其他示例查询
let queries = vec![
    "过去 24 小时哪些线路过载?",              // → AnomalySearch
    "比较 1 号和 2 号变压器今天的负荷",        // → Comparison
    "5 分钟内可能跳闸的设备",                  // → Prediction
    "生成上月运行报告",                        // → Report
    "为什么 5 号母线电压偏低",                 // → RootCause
    "给 3 号发电机加 10MW",                    // → Action
];
for q in queries {
    let resp = NlQuery::ask(q).await?;
    println!("'{}' → 意图: {:?}", q, resp.intent);
}

意图矩阵

意图说明示例输出
PointInTimeQuery时间点查询昨天上午 10 点 5 号母线电压单值
RangeQuery范围查询过去 1 小时的频率曲线时序数据
AnomalySearch异常搜索哪些设备过载异常列表
Comparison对比分析比较 A 和 B 变电站对比表
Prediction预测查询下小时负荷预测预测曲线
Report报表生成生成运行报告文档
RootCause根因分析为什么电压偏低因果链
Action操作执行给 3 号发电机加 10MW命令确认

多轮对话

use eneros_viz::nl::{NlConversation, Context};

let mut conversation = NlConversation::new()
    .context(Context::new()
        .user("alice")
        .role("dispatcher"));

// 第一轮
let r1 = conversation.ask("5 号母线电压是多少?").await?;
// r1: 5 号母线当前电压 1.02 pu

// 第二轮(上下文延续)
let r2 = conversation.ask("过去 1 小时呢?").await?;
// r2: 5 号母线过去 1 小时电压曲线 + 统计

// 第三轮
let r3 = conversation.ask("为什么偏低?").await?;
// r3: 根因分析 + 建议动作

报表导出

支持 PDF、Excel、Word 三种格式,内置电力行业模板:

use eneros_viz::report::{Report, ReportFormat, Template, Chart, Range};

let report = Report::new()
    .template(Template::daily_operation())
    .period(Range::last(Duration::from_secs(86400)))
    .data(daily_data)
    .charts(vec![
        Chart::load_curve()
            .width(800).height(400),
        Chart::voltage_profile()
            .width(800).height(300),
        Chart::frequency_deviation()
            .width(800).height(300),
        Chart::generation_mix()
            .ty(ChartType::Pie),
    ])
    .tables(vec![
        Table::outage_statistics(),
        Table::equipment_status(),
    ])
    .metadata(Metadata::new()
        .author("EnerOS")
        .title("2026-07-06 日报")
        .confidentiality(Confidentiality::Internal));

// 导出为不同格式
report.export(ReportFormat::Pdf, "daily.pdf").await?;
report.export(ReportFormat::Excel, "daily.xlsx").await?;
report.export(ReportFormat::Word, "daily.docx").await?;
report.export(ReportFormat::Html, "daily.html").await?;

报表模板

模板内容适用
daily_operation日运行报告日常运维
weekly_summary周汇总周报
monthly_review月度回顾月报
incident_analysis事故分析事后复盘
equipment_health设备健康度检修计划
economic_dispatch经济调度调度优化
compliance_audit合规审计监管报送

定时报表

use eneros_viz::report::{ReportScheduler, Schedule, Day};

let scheduler = ReportScheduler::new()
    .schedule("daily", Template::daily_operation(),
        Schedule::daily(8, 0))
    .schedule("weekly", Template::weekly_summary(),
        Schedule::weekly(Day::Monday, 8, 0))
    .schedule("monthly", Template::monthly_review(),
        Schedule::monthly(1, 8, 0))
    .recipients(vec!["ops@grid.com", "manager@grid.com"])
    .format(ReportFormat::Pdf)
    .subject("EnerOS {{template}} - {{date}}")
    .on_failure(|err| async move {
        notify_admin("报表生成失败").await
    });

scheduler.start().await?;

实时仪表盘

use eneros_viz::dashboard::{Dashboard, Widget, Range, MapLayer, AlertFilter};

let dashboard = Dashboard::new("实时监控")
    .layout(Layout::grid(3, 3))
    .widget(Widget::single_line("频率", "grid.frequency",
        Range::last(Duration::from_secs(60))))
    .widget(Widget::gauge("总负荷", "grid.total_load", 0.0, 1000.0)
        .thresholds(vec![
            Threshold::warn(800.0),
            Threshold::critical(950.0),
        ]))
    .widget(Widget::map("电网地图", MapLayer::substations()
        .zoom(8)
        .center(31.23, 121.47)))
    .widget(Widget::alert_list("告警", AlertFilter::active()
        .severity_gte(Severity::Warn)))
    .widget(Widget::bar_chart("分区负荷", "feeder.load"))
    .widget(Widget::heatmap("设备温度", "device.temperature"))
    .widget(Widget::topology("拓扑图", TopologyConfig::auto()))
    .widget(Widget::status_grid("设备状态", DeviceFilter::all()))
    .refresh(Duration::from_secs(1));

dashboard.serve("0.0.0.0:8080").await?;

Widget 类型

Widget数据类型更新频率
single_line时序1s
gauge实时值1s
bar_chart离散值5s
pie_chart占比30s
map地理5s
topology拓扑事件
heatmap矩阵5s
alert_list告警实时
status_grid状态5s
table表格30s
3d_scene3D按需

数据可视化类型

类型用途交互
单值卡片KPI 监控钻取
曲线图时序数据缩放 / 框选
柱状图对比分析钻取
饼图占比分布钻取
地图地理分布点击
拓扑图电网结构拖拽 / 钻取
热力图负载分布框选
3D 场景站内巡视旋转 / 漫游
散点图关联分析框选
桑基图能量流钻取

性能指标

操作延迟备注
glTF 场景生成(1000 设备)< 500ms含 Draco 压缩
glTF 场景生成(10000 设备)< 3s含 Draco 压缩
自然语言意图识别< 200msLLM 调用
自然语言查询执行< 1s含 SQL 翻译
PDF 报表生成(含图表)< 2sA4 10 页
Excel 报表生成(10k 行)< 1s含格式化
Word 报表生成< 3s含图表
仪表盘刷新(10 widgets)< 100msWebSocket 推送
拓扑图渲染(1000 节点)< 500msSVG
多轮对话响应< 2s含上下文

与其他能力的关系

相关文档