Skip to main content

Visualization and Interaction Enhancement

Capabilities

Visualization and Interaction Enhancement

EnerOS v0.46.0 significantly enhances visualization and interaction capabilities, adding 3D scene glTF 2.0 export, natural language query engine (8 intent recognition), and PDF/Excel/Word report export. Operators can interact with grid data through natural language without learning complex query languages. The visualization capability is provided by the eneros-viz crate.

Visualization Architecture

┌──────────────────────────────────────────────────────────────┐
│                     Interaction Layer                         │
│  Natural Language Query / Dashboard / Web UI / Mobile        │
└────────┬─────────────────────────────────────────────────────┘

┌────────▼─────────────────────────────────────────────────────┐
│                     Rendering Layer                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │
│  │ 3D Scene │  │ Topology │  │ Charts   │  │ Reports  │    │
│  │ glTF 2.0 │  │ SVG/Canvas│  │ ECharts │  │ PDF/XLSX│    │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘    │
└────────┬─────────────────────────────────────────────────────┘

┌────────▼─────────────────────────────────────────────────────┐
│                     Data Layer                                │
│  Time-Series Database / Topology Model / Alerts / Agent State│
└──────────────────────────────────────────────────────────────┘
ModuleOutput FormatApplicable
3D SceneglTF 2.0 / GLBIn-station inspection, equipment inspection
Natural Language QueryJSON / TextOperations interaction
Report ExportPDF / Excel / WordReport archival
DashboardHTML / WebSocketReal-time monitoring
Topology DiagramSVG / CanvasGrid structure
ChartsPNG / SVGTrend analysis

3D Scene

Renders grid topology and device status as 3D scenes, supporting glTF 2.0 standard export, loadable in rendering engines such as 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"));

// Add substation
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));

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

// Add device status
scene.add_device(DeviceMesh::transformer("t1")
    .position_in("sub_a")
    .load(0.78)
    .temperature(65.0));

// Color by status
scene.color_by(ColorScheme::VoltageBand);  // Color by voltage

// Add annotations
scene.annotation(Annotation::label("sub_a", "220kV Substation A"));

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

Scene Hierarchy

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

Color Schemes

SchemeDescriptionColor Mapping
VoltageBandBy voltage levelRed (>1.05) / Green (0.95-1.05) / Blue (<0.95)
LoadRatioBy load ratioRed (>90%) / Yellow (70-90%) / Green (<70%)
DeviceStatusBy device statusRed (fault) / Yellow (warning) / Green (normal)
OutageAreaOutage areaGray (power loss) / Colorful (normal)
RealtimeFlowReal-time power flowFlow direction animation

Natural Language Query

8 intent recognition types, no need to learn query syntax, supports Chinese natural language:

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

let response = NlQuery::ask("What was the bus 5 voltage at 10 AM yesterday?").await?;

match response.intent {
    Intent::PointInTimeQuery => {
        println!("Bus 5 10:00 voltage: {:.2} kV", response.value);
        println!("SQL equivalent: {}", response.translated_sql);
    }
    Intent::RangeQuery => {
        println!("Data points count: {}", response.data_points.len());
    }
    _ => {}
}

// Other example queries
let queries = vec![
    "Which lines were overloaded in the past 24 hours?",              // → AnomalySearch
    "Compare today's load of transformer 1 and 2",        // → Comparison
    "Devices that may trip within 5 minutes",                  // → Prediction
    "Generate last month's operation report",                        // → Report
    "Why is bus 5 voltage low",                 // → RootCause
    "Add 10MW to generator 3",                    // → Action
];
for q in queries {
    let resp = NlQuery::ask(q).await?;
    println!("'{}' → intent: {:?}", q, resp.intent);
}

Intent Matrix

IntentDescriptionExampleOutput
PointInTimeQueryPoint-in-time queryBus 5 voltage at 10 AM yesterdaySingle value
RangeQueryRange queryFrequency curve over past 1 hourTime-series data
AnomalySearchAnomaly searchWhich devices are overloadedAnomaly list
ComparisonComparison analysisCompare substations A and BComparison table
PredictionPrediction queryNext hour load forecastPrediction curve
ReportReport generationGenerate operation reportDocument
RootCauseRoot cause analysisWhy voltage is lowCausal chain
ActionAction executionAdd 10MW to generator 3Command confirmation

Multi-turn Dialogue

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

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

// First turn
let r1 = conversation.ask("What is bus 5 voltage?").await?;
// r1: Bus 5 current voltage 1.02 pu

// Second turn (context continuation)
let r2 = conversation.ask("What about the past 1 hour?").await?;
// r2: Bus 5 voltage curve over past 1 hour + statistics

// Third turn
let r3 = conversation.ask("Why is it low?").await?;
// r3: Root cause analysis + suggested actions

Report Export

Supports PDF, Excel, Word formats, with built-in power industry templates:

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 Daily Report")
        .confidentiality(Confidentiality::Internal));

// Export to different formats
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?;

Report Templates

TemplateContentApplicable
daily_operationDaily operation reportDaily operations
weekly_summaryWeekly summaryWeekly report
monthly_reviewMonthly reviewMonthly report
incident_analysisIncident analysisPost-incident review
equipment_healthEquipment healthMaintenance planning
economic_dispatchEconomic dispatchDispatch optimization
compliance_auditCompliance auditRegulatory reporting

Scheduled Reports

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("Report generation failed").await
    });

scheduler.start().await?;

Real-time Dashboard

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

let dashboard = Dashboard::new("Real-time Monitoring")
    .layout(Layout::grid(3, 3))
    .widget(Widget::single_line("Frequency", "grid.frequency",
        Range::last(Duration::from_secs(60))))
    .widget(Widget::gauge("Total Load", "grid.total_load", 0.0, 1000.0)
        .thresholds(vec![
            Threshold::warn(800.0),
            Threshold::critical(950.0),
        ]))
    .widget(Widget::map("Grid Map", MapLayer::substations()
        .zoom(8)
        .center(31.23, 121.47)))
    .widget(Widget::alert_list("Alerts", AlertFilter::active()
        .severity_gte(Severity::Warn)))
    .widget(Widget::bar_chart("Feeder Load", "feeder.load"))
    .widget(Widget::heatmap("Device Temperature", "device.temperature"))
    .widget(Widget::topology("Topology", TopologyConfig::auto()))
    .widget(Widget::status_grid("Device Status", DeviceFilter::all()))
    .refresh(Duration::from_secs(1));

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

Widget Types

WidgetData TypeUpdate Frequency
single_lineTime-series1s
gaugeReal-time value1s
bar_chartDiscrete values5s
pie_chartProportions30s
mapGeographic5s
topologyTopologyEvent
heatmapMatrix5s
alert_listAlertsReal-time
status_gridStatus5s
tableTable30s
3d_scene3DOn-demand

Data Visualization Types

TypePurposeInteraction
Single value cardKPI monitoringDrill-down
Line chartTime-series dataZoom / Select
Bar chartComparison analysisDrill-down
Pie chartProportion distributionDrill-down
MapGeographic distributionClick
Topology diagramGrid structureDrag / Drill-down
HeatmapLoad distributionSelect
3D sceneIn-station inspectionRotate / Roam
Scatter plotCorrelation analysisSelect
Sankey diagramEnergy flowDrill-down

Performance Metrics

OperationLatencyRemarks
glTF scene generation (1000 devices)< 500msIncluding Draco compression
glTF scene generation (10000 devices)< 3sIncluding Draco compression
Natural language intent recognition< 200msLLM call
Natural language query execution< 1sIncluding SQL translation
PDF report generation (with charts)< 2sA4 10 pages
Excel report generation (10k rows)< 1sIncluding formatting
Word report generation< 3sIncluding charts
Dashboard refresh (10 widgets)< 100msWebSocket push
Topology rendering (1000 nodes)< 500msSVG
Multi-turn dialogue response< 2sIncluding context

Relationship with Other Capabilities