MyEMS Architecture Design Document
Download: MyEMS开源能源管理系统架构设计文档.pdf
| Item | Content |
|---|---|
| Document | MyEMS Open-Source Energy Management System Architecture Design |
| Document version | 6.9.0 |
| System version | Community Edition v6.9.0 |
| Prepared by | Beijing Haisoft Software Co., Ltd. |
| Date | October 2026 |
This document describes the overall architecture, component boundaries, data organization, interface conventions, and deployment shape of MyEMS Community Edition, based on the actual code layout and database structure. Where this document disagrees with the source code or the official docs, the source code and the official docs prevail.
1 Introduction
1.1 Purpose
The document is a shared technical baseline for architecture review, implementation, operations, and extension. It does two things:
- It pulls the website feature notes, component READMEs, and the database design into one architecture view, so implementers do not have to reconstruct the system only by reading source code.
- It states the trade-offs, so readers know which boundaries can be extended and which ones, once broken, ripple across many components.
This document does not replace the installation guide or the API manual.
1.2 Scope and readers
The document covers all 11 Community Edition components (including the database) and the 13 business databases. It does not describe Enterprise Edition features in implementation detail.
| Reader | Suggested chapters |
|---|---|
| Technical leads and architects | Chapters 3, 4, 6, and 11, for technology choices and extension boundaries |
| Implementation and operations engineers | Chapters 5, 7, 9, and 10, for deployment, troubleshooting, and capacity |
| Developers extending the system | Chapters 5, 6, 8, and 12, for integration and new features |
Performance and capacity figures are estimates from the data structures and service shape. They have not been measured under production load. Those paragraphs are marked as estimates.
1.3 Terms
| Term | Meaning in the system |
|---|---|
| Energy category | A metered energy type, such as electricity, water, natural gas, chilled/hot water, or steam. It carries the unit of measure plus kgce and kgCO2e factors. Table: tbl_energy_categories |
| Energy item | Use-based breakdown under a category, such as HVAC, power, or lighting. Table: tbl_energy_items |
| Space | The hierarchy that organizes energy data (campus, building, floor, room). A self-reference on parent_space_id forms the tree, which is the usual filter for reports |
| Meter | The smallest business unit of metering. It binds one energy category; its points hold the readings. Offline meters (manual entry) and virtual meters (expression) are derived forms |
| Point | The smallest collected value. Types are analog, digital, energy, and text. object_type and object_id attach a point to a meter or equipment |
| Data source | A group of targets that share a protocol and connection. The connection field stores address and interval as JSON |
| Combined equipment | A system made of several pieces of equipment and meters (for example a chiller plant), used to account for input and output energy |
| 8760 model | An annual hourly model (8,760 hours). Table names end with _8760. Used for forecasting and budgeting |
| kgce / kgco2e | Kilograms of coal equivalent and kilograms of CO2 equivalent, the common conversion basis for energy and carbon |
This table is a reading aid, not a data dictionary. Field-level definitions are in Chapter 6 and the database design document.
1.4 References
- MyEMS website: https://myems.cn
- MyEMS repository (Gitee): https://gitee.com/myems/myems
- Community Edition feature list
- Introduction and compatibility list
- Database design document
- API manual
- Docker Compose installation guide
- Administrator manual
- GB/T 23331-2020, Energy management systems — Requirements with guidance for use (identical to ISO 50001:2018). Published 2020-11-19, effective 2021-06-01.
2 System overview
2.1 Positioning
MyEMS is an open-source energy management system for energy-using organizations. It follows GB/T 23331-2020 (ISO 50001:2018) and covers collection, analysis, and reporting of electricity, water, gas, cooling, and heat for buildings, factories, malls, hospitals, campuses, and energy-carbon centers. It keeps three accounting lines: energy, cost, and carbon. Public materials cite more than a thousand project cases and CMA testing. The code is MIT-licensed. The Community Edition requires the copyright notice and logo to be kept.
Compared with a generic SCADA package or a plain data platform, MyEMS centers on modeling metering relationships. Spaces, meters, equipment, tenants, stores, shopfloors, and combined equipment share one set of aggregated results, so the same energy figures can be opened along organizational, physical, and commercial dimensions. That choice makes the data model heavier than a typical monitoring system, and it drives most of the architecture decisions below.
2.2 Functional domains
- Collection. Modbus TCP, BACnet/IP, Siemens S7, and OPC UA connect field instruments and controllers. Offline meters and Excel import cover points that cannot be networked or must be read by hand.
- Monitoring and analysis. Meter trends, master/sub-meter balance, batch and comparison analysis, power quality, and category, item, load, efficiency, and statistics for spaces and equipment. Intensity can be per area or per product.
- Accounting. Energy is priced into cost and factored into carbon. Reports compare a reporting period with a baseline period. Plans, baselines, and forecasts are supported.
- Reporting. Analysis pages export real xlsx files with tables and line, bar, and pie charts, not CSV text. Advanced reports add templates, schedules, and email delivery.
- Configuration. Spaces, meters, points, data sources, protocols, equipment, combined equipment, cost centers, tariffs, working calendars, users, and privileges.
- Extensions. The Community Edition includes AI conclusions and saving advice through DeepSeek and the OpenAI API. Photovoltaics, storage, EV chargers, microgrids, virtual power plants, control, fault diagnosis, and work orders are Enterprise Edition capabilities.
2.3 System boundary
Upstream are meters, PLCs, building automation, and PV/storage monitoring interfaces. Downstream are browsers, mobile clients, large screens, and third-party systems on the REST API. Inside the boundary the system fetches numbers, calculates them, displays them, and emits reports. It does not provide SCADA graphics, trade-settlement metrology, a message bus, or a stream processor.
A direct consequence: freshness is defined by the collection interval (configured per data source; the sample is 60 seconds). Components exchange data only through database tables. There is no event bus. That fact is the premise of the runtime shape in Chapter 4 and the decisions in Chapter 11.
2.4 Version and license
The current release is v6.9.0, the 49th release. The cadence is one minor version per month and one major version per year. Upgrades must apply the schema changes in the CHANGELOG and the upgrade document.
The license is MIT. Commercial use and modification are allowed, but the Community Edition requires MyEMS copyright text and the logo to remain. Renaming or replacing the brand is explicitly unsupported. That compliance boundary must be confirmed before white-label delivery.
3 Goals and principles
3.1 Design goals
- One model for many protocols and energy types. Heterogeneous instruments are normalized onto the same point model. A new protocol must not pull upper layers with it.
- Traceable accounting. Every energy, cost, and carbon figure can be traced to a meter, a point, and a time range, and keeps a data-quality flag.
- Many dimensions. The same energy supports space, equipment, tenant, store, shopfloor, and combined-equipment views, including recursive hierarchy queries.
- Low-friction delivery. A single host can be deployed with docker-compose, on several Chinese operating systems and MySQL-family databases, including private and offline sites.
- Extension and integration. The external surface is the REST API plus JSON import/export. The internal surface is the table structure. New protocols and reports can be added without changing the core services.
3.2 Design principles
- One source of truth. Each measurement is produced by one point. Every derived value can be recomputed from it. There is no second raw series that must be maintained by hand.
- Compute and storage are split by purpose. Raw series, normalized energy, multi-dimensional aggregates, and derived accounting values live in different databases, so one database does not have to absorb every workload.
- Dimensions are materialized ahead of time. Aggregates are stored as object × direction × classification × grain. Storage is spent so report pages do not scan at request time.
- Time is UTC. Every timestamp is stored in UTC and converted to local time in the presentation layer.
- Configuration-driven. Targets, intervals, point addresses, and aggregation scope come from the database. Service processes stay as stateless as practical, so they can be containerized and restarted.
- Recomputable. Any calculation stage can be rerun for a time range.
is_publishedandis_baddecide whether a value reaches an external report, so a wrong factor does not require rebuilding the whole database.
3.3 Constraints
The stack is fixed. The backend is Python. The API uses Falcon on gunicorn or waitress. The web UI is React and the admin UI is AngularJS 1.x. The database is the MySQL family. The published compatibility list includes MySQL 8.0/5.7, MariaDB 11/10, Percona Server, SingleStore 8.0/7.8, GoldenDB 3.0/2.0, and OceanBase 4.2/3.2. The Enterprise Edition also supports SQL Server, Oracle, and GreatDB.
That list is both a capability and a constraint. Cross-database compatibility means the system can use only the SQL subset those products share. It cannot depend on a vendor window function, JSON function, or partitioning syntax. That is why Chapter 6 expresses business meaning in table structure rather than in SQL.
4 Overall architecture
4.1 Layered architecture
There are five logical layers. Collection turns field signals into raw values. Storage holds raw, aggregated, and derived values by purpose. Computation turns raw values, on a time cadence, into multi-dimensional energy, cost, and carbon. The service layer exposes query, calculation, and export over REST. Presentation splits into an analysis UI and an administration UI. Cross-cutting concerns (privileges, configuration, languages, logs, containers, multi-database support) sit beside the layers.

The layers match deployment units. Each collection and computation component is its own process or container and can be started, stopped, and scaled alone. The service layer is the only external HTTP entry. Presentation is two sets of static files. Process isolation on a single host means a failing protocol collector does not take down the API.
4.2 Layer responsibilities
Collection. One service per protocol reads registers or nodes on the data-source interval and writes the raw time-series database. The service does not interpret business meaning. It parses the protocol and converts types. Which business object a point belongs to is object_type and object_id in the configuration database.
Storage. Thirteen databases hold different kinds of data. The system database holds master data. The historical database holds raw series. Energy, billing, and carbon hold aggregates. The remaining databases hold baselines, models, plans, predictions, diagnostics, users, reports, and production. There are no foreign keys across databases. The application joins them.
Computation. Three services form a pipeline. Normalization turns raw values into hourly energy. Cleaning flags or corrects bad values. Aggregation materializes seven object types at four time grains. Each stage takes a time range, so each stage can be rerun.
Service. myems-api is the only data exit. It authenticates, routes resources, builds reports, and exports Excel. It holds no business state. State lives in the database and the session table, so several API instances can share one database.
Presentation. The web UI is for energy analysts: charts, reports, and export. The admin UI is for administrators: master data and collection configuration. Both are static sites served by nginx, which reverse-proxies the API.
4.3 Components and how they collaborate
Components do not call each other. They couple through tables. Collectors write the historical database. Computation reads it and writes result databases. The API reads all of them. The configuration database is the most referenced node: collection, computation, authentication, and reporting all read it.

A shared database is simple to deploy, needs no middleware, and runs offline. The cost is that the contract between components is hidden in table structure. A schema change is a cross-component breaking change. Chapter 11 treats that as technical debt that has to be managed.
| Component | Runtime | Stack | Responsibility | Port |
|---|---|---|---|---|
| myems-database | Initialization scripts | MySQL DDL | Creates the 13 databases, all tables, and seed data | — |
| myems-api | Long-running service | Python, Falcon, gunicorn/waitress, openpyxl | REST, authentication, reports, Excel export | 8000 |
| myems-admin | Static site | AngularJS 1.x, nginx | Master data and collection configuration | 8001 |
| myems-web | Static site | React 16, ECharts, Chart.js, i18next, nginx | Energy analysis and visualization | 80 |
| myems-modbus-tcp | Long-running collector | Python, modbus_tk, schedule | Modbus TCP/RTU collection | — |
| myems-bacnet | Long-running collector | Python, BACnet/IP | Building-automation collection | — |
| myems-opc-ua | Long-running collector | Python, OPC UA | Reads an OPC UA server | — |
| myems-s7 | Long-running collector | Python, S7 | Siemens PLC collection | — |
| myems-normalization | Scheduled job | Python, mysql-connector, schedule | Raw values to hourly energy | — |
| myems-cleaning | Scheduled job | Python, schedule | Detect, correct, and flag bad data | — |
| myems-aggregation | Scheduled job | Python, mysql-connector | Multi-dimensional aggregates and derived accounting | — |
Ports are defaults and can be changed in nginx.conf and the container compose file. Collectors and computation services do not listen. They only open connections to the database and to field devices.
4.4 Runtime shapes
- Request/response. myems-api and the two front ends. Short requests, concurrency that follows the user count, failures that users notice immediately. Watch process health, the connection pool, and slow queries.
- Periodic polling. The four collectors. They sleep, wake on the data-source interval, and a single failure does not cancel the next cycle. Watch reachability and consecutive failures.
- Scheduled batches. Normalization, cleaning, and aggregation. They process a time range, concentrate database load, and a failure accumulates lag. Watch whether the schedule is stuck and whether each period is filled.
Failures look different. An API failure is seen at once. A collection failure shows up as missing data, often days later in a report. A batch failure shows up as aggregate tables that stop growing. The monitoring in Chapter 10 is split along these lines.
5 Component design
5.1 Protocol collectors
The four collectors share a skeleton: read matching data sources from the configuration database, parse address and interval from connection, read each point address, convert, and write the historical tables. They do not share code, but the contract is the same, so a new protocol can copy the skeleton.
For Modbus TCP, the data-source connection stores host, port, and interval. The point address stores slave id, function code, register offset, register count, data format, and a byte-swap flag. Function codes 01, 02, 03, 04, and 23 are supported. Byte order is a Python struct format. byte_swap swaps words of 32-bit or 64-bit values.
Data-source connection:
{"host":"10.9.67.99","port":502,"interval_in_seconds":60}
Point address:
{"slave_id":1,"function_code":3,"offset":0,"number_of_registers":2,"format":"<f","byte_swap":true}
Protocol differences are compressed into two JSON documents, so field commissioning happens in the admin UI rather than in code.
A hard constraint: after a data source or point changes, the collector must be restarted. Configuration is loaded at process start. There is no hot reload. The standard action is change configuration, restart the service, then check the data.
5.2 Normalization
Normalization turns raw point values into energy that has business meaning. Raw tables store instantaneous readings per point. Reports need “how much energy this meter used in this hour”, which requires aggregation and differencing along the meter-to-point binding. The service works in hourly slices, writes tbl_energy_value, and updates the matching _latest table.
This is the first time a value has an energy category and a unit. A wrong meter-to-point binding propagates into cost and carbon and is not rejected by a later stage. Checking those bindings is a prerequisite of acceptance.
5.3 Cleaning
Cleaning handles three cases: values outside a physical range, missing values inside a period, and cumulative values that run backwards (meter replacement or a communication fault). Some anomalies are flagged is_bad and excluded from later aggregation. Others are corrected by rule. is_published controls whether the value is released.
The point of the layer is to make data quality explicit. Most report disputes are bad data that was never isolated, not a wrong algorithm. The two Boolean flags keep quality next to the value, so a report can show both the number and how much it should be trusted.
5.4 Aggregation
Aggregation is where data volume grows, and it is the performance-critical stage. It reads normalized energy and computes seven object types (meter, equipment, combined equipment, space, store, tenant, shopfloor; meters also include offline and virtual meters) at four grains (hour, day, month, year) into the energy database. The same energy multiplied by the tariff is written to billing, and multiplied by the emission factor is written to carbon.
Cost and carbon are not collected. They are linear transforms of energy. After a tariff or factor change, rerunning the period corrects history without recollecting. The cost of that simplification is that time-of-use and tiered tariffs are non-linear and must be interpreted inside aggregation. They cannot be a single coefficient.
5.5 API
The API is Falcon and is the only exit for data. Resources are plural nouns. Meters, spaces, equipment, combined equipment, points, data sources, tariffs, and commands each have a path. Hierarchy is nested in the path, for example meters under a combined equipment. Non-CRUD actions are verbs on a sub-path: run an advanced report, send a command, clone a meter, export, and bulk import.
Excel export is a real xlsx built by openpyxl on the server, including table and chart objects. Advanced reports support a template, a schedule, and email. The expression is JSON on the report definition and describes the space scope and run conditions.
The service holds no business state. Sessions are in tbl_sessions and API keys are in tbl_api_keys. Multiple instances only need to share the database, which matters when many projects are hosted together.
5.6 Presentation
The web UI is React 16, with ECharts and Chart.js, Mapbox GL and Leaflet, i18next, and react-bootstrap-table. craco overrides the default Create React App build. nginx serves the static output. Maps need mapboxToken in src/config.js and showOnlineMap set to true. Offline sites should turn that switch off.
The admin UI is AngularJS 1.x and maintains master data. Two front-end stacks are a historical result: the analysis UI keeps moving, the configuration UI stays stable. New configuration features are written in AngularJS. New analysis features are written in React. The team has to maintain both.
Admin fields map almost one-to-one onto table columns. That makes the model easy to learn, and it means a schema change usually requires an admin UI change.
5.7 Database as the integration bus
The database is not passive storage. Components cooperate by table contract: writers guarantee field meaning and the time basis, readers parse that contract. A renamed field or a changed type is a breaking change across components.
Every database uses utf8mb4, utf8mb4_unicode_ci, and InnoDB. Names follow myems_{function}_db, tbl_{entity}, lower_snake_case columns, and tbl_{table}_index_{n}. When data is assembled across databases, names and meanings are predictable.
5.8 Contracts between components
- Time. All timestamps are UTC. Historical tables use
utc_date_time. Aggregate tables usestart_datetime_utcas the start of the period. Presentation converts to local time. Day and month boundaries are therefore UTC. If the business needs a local calendar, the report has to correct it. - Numbers.
DECIMAL(21,6)holds six decimal places, which covers typical kWh, m³, currency, and kgCO2e values without binary floating-point drift. - Identity. Entity tables have
id(BIGINT auto-increment) anduuid(CHAR(36)).idis the in-database link.uuidaligns objects across systems and across import/export, so auto-increment keys do not collide between environments. - Quality and status.
is_bad,is_published,is_counted, andis_enabledcontrol trust, publication, whether the value is counted, and whether configuration is enabled. Message tables use astatusstring such asnew→sent→acknowledged.
6 Data architecture
6.1 Data layers
| Layer | Content | Where |
|---|---|---|
| L0 raw points | The only source of measurements, append-only except for cleaning corrections | Historical database |
| L1 normalized energy | Rolled up to a meter, with category and unit | tbl_energy_value in the historical database |
| L2 dimensional aggregates | Materialized by object, direction, classification, and grain. This is what reports read | Energy database |
| L3 derived accounting | L2 multiplied by a factor, or produced by an algorithm | Billing, carbon, baseline, plan, prediction, and model databases |
The layers exist so a recalculation can be bounded. A factor change reruns L3. A wrong meter binding reruns from L1. Only a collection error goes back to L0. Knowing the boundary keeps a factor correction to hours rather than a full rebuild.
6.2 Database split
The 13 databases fall into four groups: configuration (system, user), raw (historical), aggregates (energy, billing, carbon), and derived or supporting (baseline, model, plan, prediction, fdd, reporting, production).
| Database | Purpose | Scale | Main writers | Main readers |
|---|---|---|---|---|
| myems_system_db | Categories, items, spaces, meters, equipment, combined equipment, points, data sources, cost centers, tariffs, distribution systems, energy-flow diagrams, commands, control modes | Most tables (about 150), small volume, complex relations | Admin UI through the API | Collectors, computation, API |
| myems_historical_db | Raw series, latest-value cache, and file tables for offline meters, cost, repair, and plans | Largest, grows with points and timestamps | Collectors, import | Normalization, API real-time analysis |
| myems_energy_db | Multi-dimensional energy at hour/day/month/year | Rows depend on objects and dimensions | Aggregation | API reports |
| myems_billing_db | Cost aggregates, same shape as energy, already priced | Same | Aggregation | API cost reports |
| myems_carbon_db | Carbon aggregates, same shape, already factored | Same | Aggregation | API carbon reports |
| myems_energy_baseline_db | Baselines for savings comparison | Small | Aggregation / import | API analysis |
| myems_energy_model_db | 8,760-hour annual model | 8,760 rows per object | Aggregation / generation | API forecast and budget |
| myems_energy_plan_db | Plans and targets | Small | Plan-file import | API comparison |
| myems_energy_prediction_db | Predicted energy | Small | Prediction jobs | API |
| myems_fdd_db | Fault rules and web, email, SMS, and WeChat messages | Small, complex rule expressions | Rule engine, API | API, message delivery |
| myems_user_db | Users, privileges, sessions, API keys, audit logs, notifications | Small, high security | API | API authentication |
| myems_reporting_db | Report definitions, templates, generated files, email | Small, includes binary attachments | Report jobs | API |
| myems_production_db | Products, teams, shifts, and output, for energy per product | Small | Admin UI, import | API intensity analysis |
6.3 Domain model
The core is an ownership chain. A data source produces points. A point belongs to a meter or equipment. A meter belongs to a space and binds an energy category. Spaces form a tree through the parent pointer, and stores, tenants, and shopfloors hang on that tree. Combined equipment groups equipment and meters into one system. Dictionaries (category, item, cost center, tariff, product, contact) are referenced by foreign key.

Two common shortcuts are deliberately avoided. Association columns are not duplicated onto entity tables. Many-to-many links live in their own tables (tbl_spaces_meters, tbl_equipments_meters), so one meter can serve several space views. There is also no separate fact-table family per object type. One naming rule is reused, so report logic can be parameterized by object type.
The cost is multi-hop queries. “Lighting electricity of this floor this month” needs a space-tree walk, an association-table expansion, and an energy-item filter. That is why the system pre-aggregates instead of joining at request time.
6.4 Time-series tables
The four historical value tables (analog, digital, energy, text) share point_id + utc_date_time + actual_value, plus is_bad and is_published. Each has a _latest table so real-time pages and equipment status do not run MAX over a table of tens of millions of rows.
Indexes are the composite (point_id, utc_date_time) and the single column (utc_date_time). The first serves “this point in this range”. The second serves “every point in this range” for batch jobs. The pair matches the two access patterns: online query and offline computation.
Files (offline-meter files, cost files, repair files, plan files, report files, and templates) are stored as LONGBLOB, with file_name, uuid, upload_datetime_utc, and status. Backup and migration then only have to move the database. The database grows with attachments, so a project should set a file-size limit and a cleanup policy.
6.5 Aggregate table names
Tables are named tbl_{object}_{direction}_{classification}_{grain}.
- Object:
meter,offline_meter,virtual_meter,equipment,combined_equipment,space,store,tenant,shopfloor - Direction:
input,output - Classification:
category(energy category),item(energy item) - Grain:
hourly,daily,monthly,yearly
| Object | Direction and classification | Example |
|---|---|---|
| Meter | none | tbl_meter_hourly |
| Offline / virtual meter | none | tbl_virtual_meter_hourly |
| Equipment | input or output / category or item | tbl_equipment_input_category_hourly |
| Combined equipment | input or output / category or item | tbl_combined_equipment_output_item_hourly |
| Space | input or output / category or item | tbl_space_input_category_daily |
| Store / tenant / shopfloor | input / category or item | tbl_tenant_input_item_monthly |
| PV / storage / microgrid | charge, discharge, grid buy, grid sell | tbl_energy_storage_container_charge_hourly |
The physical schema is a subset of that cross product, not the full matrix. Seeing the name is enough to infer the dimensions, and a new object can follow the same rule.
6.6 Accounting basis
Coal equivalent, money, and carbon dioxide all start from the same energy value. Only the coefficient changes. tbl_energy_categories.kgce and kgco2e hold the factors. Tariffs hold the cost coefficient.
The seeded example for electricity is 0.123 kgce/kWh and 0.928 kgCO2e/kWh. Those numbers are examples. A project must replace them with the local statistical basis, the grid emission factor, and the contract tariff. Otherwise every carbon and cost report is systematically wrong. A basis change should be recorded as a configuration change plus a period rerun, not as an edit of historical values.
Time-of-use and tiered tariffs are interpreted during aggregation. Carbon factors can differ by energy category, but there is no factor-version table. History after a factor change depends on a rerun.
6.7 Capacity and retention
The raw time-series database is the only one that grows linearly with point count and sample rate. At 3,000 points and a 15-minute interval, that is about 288,000 rows per day and about 105 million rows per year. At about 40 bytes per row including indexes, that is about 3.9 GiB per year. A 1-minute interval multiplies the growth by 15, and then time partitioning or archival is required. (Estimate.)
Aggregate databases grow with object count and dimension combinations, not with point count. At 500 objects, 4 grains, and 2 classifications, one dimension is about 4.38 million hourly rows per year, which is manageable. Scaling pressure comes from collection, not from reports. (Estimate.)
Suggested retention: keep raw values for the shortest period that still allows audit and recalculation (often 1 to 3 years), keep aggregates and derived values long term, and clean files on a project schedule. There is no built-in archival job. Cleanup has to be a database job, and the recalculation window must be confirmed before data is removed.
7 Data flow
7.1 End-to-end path
A value passes seven stages from the field to a report: field read, protocol parse, raw insert, normalization, cleaning, aggregation, and query (Figure 2). Each stage has explicit input and output tables and aligns on a time range, so any stage can be rerun on its own.
The cadences differ. Collection is on the order of minutes, normalization, cleaning, and aggregation are hourly, and reports are request-scoped. A report usually lags by one period. That lag is by design. Training should say so, so it is not treated as “the live data is wrong”.
7.2 Collection and raw insert
The collector wakes on the data-source interval, reads each point, writes the historical database, and refreshes _latest. The key is point plus timestamp. Whether a second write of the same timestamp is idempotent depends on the implementation. Operationally, one point should be owned by one collector, so two writers do not collide on the same point and timestamp.
7.3 Normalization and cleaning
Normalization reads raw values for an hour, applies the meter-to-point binding, and writes the energy-value table. Cleaning follows and handles out-of-range, missing, and backwards values. The order must not be reversed. Values have to be rolled up to a meter before “is this abnormal for this meter” can be judged.
7.4 Aggregation and derived values
Aggregation does three things from one energy read: energy by object and dimension, cost by tariff, and carbon by factor. The three writes keep the three databases consistent for that period. Baselines, plans, predictions, and the 8760 model are produced afterwards from historical aggregates. They are a second derivation.
7.5 Query path
nginx forwards the request to the API. After authentication, configuration is read from the system database, live values from _latest, and reports from the aggregate databases. Excel is streamed from the server and is not written to disk.
The performance rule is: never do heavy calculation during the request. Report pages are fast because they read materialized tables. A question outside the prepared dimensions (an arbitrary window crossed with an arbitrary combination) falls back to scanning the historical database and should be estimated before it is promised.