Business Validity¶
A community's structure changes over time. Members join and leave, a battery gets replaced by a larger one, a contract is renegotiated, or ownership of a PV plant is split differently. RECreation keeps a dated history of every one of these changes so that past periods are always processed with the structure that applied at that time, not with today's structure.
This is called business validity: every operational record carries a start_date and an
optional end_date, and a record is only used for the days inside that period.
Why it matters
A battery is upgraded from 5 kW to 10 kW on January 14. If you later correct a measurement for January 3, the market and settlement for January 3 must still use the 5 kW battery. With business validity, they do.
Validity Rules¶
| Rule | Behavior |
|---|---|
| Precision | Whole calendar days in the Europe/Lisbon timezone. No times of day |
start_date |
Inclusive. The record applies from 00:00 Lisbon time on this day. Required on every create and update |
end_date |
Inclusive and optional. The record applies until 23:59 Lisbon time on this day. null means open-ended (still valid) |
| One-day periods | Allowed: start_date equal to end_date |
| Adjacent periods | Allowed: one period can end on January 30 and the next start on January 31 |
| Overlaps | Forbidden for the same entity. Returns 409 Conflict |
| Gaps | Allowed. During a gap the entity has no effective version and is ignored |
end_date < start_date |
Rejected with 422 Unprocessable Entity |
Versions, gaps and INACTIVE¶
Each entity has a stable identity (its id, which never changes) and any number of
versions, each with its own version_id, period and values. There are two different ways
for an entity to be "off" on a given day:
- Gap: no version covers that day. The entity effectively does not exist on that day.
INACTIVEversion: a version covers the day, but withstatus: INACTIVE. The entity still exists and is still returned by the API, but it is excluded from market, settlement and optimization.
The timeline below shows one member's history: active in January, a one-day gap on January 31, inactive in February, then active again from March onward with no end date.
%%{init: {"themeVariables": {"fontSize": "18px"}, "gantt": {"barHeight": 30, "barGap": 8}}}%%
gantt
title Membership of one member (inclusive dates)
dateFormat YYYY-MM-DD
axisFormat %d %b
section Versions
ACTIVE (Jan 1 to Jan 30) :done, a1, 2026-01-01, 2026-01-31
Gap on Jan 31, no version :crit, g1, 2026-01-31, 2026-02-01
INACTIVE (Feb 1 to Feb 28) :i1, 2026-02-01, 2026-03-01
ACTIVE (Mar 1, end_date null) :active, a2, 2026-03-01, 2026-04-15
What Is Versioned¶
| Entity | Versioned fields | Stable fields (never change) |
|---|---|---|
| Community | Allocation method, market and settlement parameters, trading fee, EMS type, status, type | id. Name, identifier and contact details are current-only |
| Membership | status, community role, is_host, is_promoter |
User account and profile (name, email, NIF...) are current-only |
| Meter | status, installation_type, production_identifier, ord, parent cpe_id |
identifier, type |
| Resource | name, status, meter_id, all type-specific details |
type |
| Ownership | perc_ownership |
The member–resource pair |
| Contract | Every field, including buy/sell tariff details | meter_id |
| RESP | is_active |
The CPE pair |
| Fixed bilateral price | price |
The provider–receiver pair |
| EMS configuration | horizon, delta_t, coefficients flags, config_data, is_active |
module_type |
Stable fields
To change a stable field, create a new entity. For example, replacing a physical meter
with a different identifier, or changing a resource from SCPU to STORAGE, means
closing the old entity (see Ending a period) and creating a new one.
Meter identifiers must be unique within a community across all time.
Time-series data is not versioned, because each row already carries its own timestamp: meter loads, DSO measurements, forecasts, opportunity costs, per-interval bilateral prices and allocation coefficients. When this data is processed, RECreation looks up the structure that was valid on that row's date.
Reading Data at a Date¶
Current view and effective_at¶
All regular GET endpoints for versioned entities return the version effective today
(Europe/Lisbon). To see the structure as it was, or as it will be, on another day, add the
effective_at query parameter:
GET /core/api/communities/{community_id}/meters?effective_at=2026-01-10
GET /core/api/communities/{community_id}/resources/{resource_id}?effective_at=2026-01-10
GET /core/api/communities/{community_id}/community_structure?effective_at=2026-01-10
Every versioned response includes the matching version_id, start_date and end_date, so
you can see which version you received and how long it applies.
%%{init: {"themeVariables": {"fontSize": "18px"}}}%%
flowchart LR
Q["GET with effective_at = D<br/>(default: today in Lisbon)"] --> F{"A version with<br/>start_date ≤ D ≤ end_date?"}
F -->|No: gap| N["Not found / omitted<br/>from lists"]
F -->|Yes| S{"status"}
S -->|ACTIVE| A["Returned and used in<br/>market, settlement and EMS"]
S -->|INACTIVE| I["Returned, but excluded<br/>from operations"]
Version history¶
Use the business-validity endpoints to list every version of an entity, ordered by
start_date:
Base path: /core/api/communities/{community_id}/business-validity
| Endpoint | Returns |
|---|---|
GET /config/versions |
Community configuration versions |
GET /memberships/{user_id}/versions |
A member's status and role timeline |
GET /meters/{meter_id}/versions |
Meter versions |
GET /meters/{meter_id}/contracts/versions |
All contracts of a meter |
GET /resources/{resource_id}/versions |
Resource versions, including type-specific details |
GET /ownerships/{resource_id}/{user_id}/versions |
Ownership share history |
GET /resp/{resp_id}/versions |
RESP versions of a CPE pair |
GET /fixed-bilateral/{receiver_user_id}/{provider_user_id}/versions |
Fixed bilateral price history |
GET /ems/{config_id}/versions |
EMS configuration versions |
GET /snapshot?effective_at=YYYY-MM-DD |
The complete structure effective on one day, with a context_hash |
Export and import¶
| Endpoint | Description |
|---|---|
GET /export?mode=snapshot&effective_at=YYYY-MM-DD |
Consistent structure snapshot on one day |
GET /export?mode=history[&start_date=...&end_date=...] |
Every version overlapping the period (all history if no dates) |
POST /import |
Restores an export. Legacy files without dates require legacy_base_effective_date, which becomes the start_date of every imported record |
Writing Data¶
Creating¶
Every create request for a versioned entity requires start_date. end_date is optional and
defaults to open-ended:
{
"identifier": "PT0002000000000000000000001234",
"type": "CPE",
"installation_type": "IC",
"status": "ACTIVE",
"community_id": "<community_uuid>",
"start_date": "2026-01-01",
"end_date": null
}
A create that overlaps an existing version of the same entity or relationship is rejected
with 409 Conflict.
Updating (dated revisions)¶
Updates (PUT / PATCH) also require start_date. An update does not overwrite the
entity. It writes a new version for the period [start_date, end_date] and trims or splits
any existing versions around it, so that history outside the period stays as it was. Fields
you leave out of the update are copied from the version that was effective on start_date.
For example, an existing open-ended version starts January 1 and you update it for February
only (start_date: 2026-02-01, end_date: 2026-02-28):
%%{init: {"themeVariables": {"fontSize": "18px"}, "gantt": {"barHeight": 30, "barGap": 8}}}%%
gantt
title Replacing February inside an open-ended version
dateFormat YYYY-MM-DD
axisFormat %d %b
section Before
Version A (Jan 1, open-ended) :done, b1, 2026-01-01, 2026-04-30
section After
Version A (Jan 1 to Jan 31) :done, a1, 2026-01-01, 2026-02-01
New version (Feb 1 to Feb 28) :active, a2, 2026-02-01, 2026-03-01
Version A copy (Mar 1, open) :done, a3, 2026-03-01, 2026-04-30
To make a change permanent from a given day, send only start_date (with end_date omitted
or null). Everything before that day is preserved and everything from that day on is
replaced.
Retroactive changes reopen sessions
Any write that touches past dates invalidates every market session in the affected
period: the session goes back to OPEN and its transactions, offers, allocations and
settlement outputs are deleted. Uploaded measurements and uploaded opportunity costs are
kept. Run market clearing and settlement again for those dates.
Ending a period¶
DELETE on a versioned entity does not erase its history. It requires the
effective_at query parameter and ends the entity from that day onward, leaving a gap:
The meter stays in the history up to and including May 31, and is absent from June 1.
%%{init: {"themeVariables": {"fontSize": "18px"}, "gantt": {"barHeight": 30, "barGap": 8}}}%%
gantt
title Ending a meter with effective_at = 2026-06-01
dateFormat YYYY-MM-DD
axisFormat %b %Y
section Before
Meter version (Jan 1, open-ended) :active, b1, 2026-01-01, 2026-09-30
section After
Meter version (Jan 1 to May 31) :done, a1, 2026-01-01, 2026-06-01
Gap from Jun 1, no version :crit, g1, 2026-06-01, 2026-09-30
Contracts, fixed bilateral prices and EMS configurations also accept an end_date query
parameter. With it, only the period [effective_at, end_date] is removed and later history is
kept.
Why not meters, resources, ownerships and memberships?
Contracts, prices and EMS configurations are commercial terms or settings, so having
nothing apply for a few days is a normal situation (for example, a gap between two retailer
contracts). Meters, resources, ownerships and memberships represent physical assets and
participants, so their DELETE only ends them from effective_at onward:
- To take a meter, resource or member out of service temporarily, send a dated update
with
status: INACTIVEfor that period instead. The entity still exists and stays visible, but it is excluded from market, settlement and optimization. - Their removal is checked against dependants. A meter cannot end while a resource is assigned to it, and a resource cannot end while it has ownerships. A hole in the middle of their history would leave those dependants pointing at something missing for part of the time.
To remove one wrong version of any entity, use the exact-version delete described below.
Note that despite this architectural decision, which simulates reality more accurately, we are still testing side effects from this distinction in terms of API usage. At the moment, the deletion of periods simply mandates a different set of API calls for different entities rather than their behavior being wholly different.
RESP relationships are never deleted. Set their state for a period instead, with
PUT /communities/{community_id}/resp/{resp_id}?effective_at=YYYY-MM-DD[&end_date=YYYY-MM-DD]
and is_active in the body.
| Entity | Ending endpoint |
|---|---|
| Member | DELETE /communities/{community_id}/users/{user_id}?effective_at= |
| Meter | DELETE /communities/{community_id}/meters/{meter_id}?effective_at= |
| Resource | DELETE /communities/{community_id}/resources/{resource_id}?effective_at= |
| Ownership | DELETE /communities/{community_id}/ownerships/{user_id}/{resource_id}?effective_at= |
| Contract | DELETE /communities/{community_id}/meters/{meter_id}/contracts/{contract_id}?effective_at=[&end_date=] |
| Fixed bilateral price | DELETE /communities/{community_id}/bilateral-prices/fixed/{receiver_user_id}/{provider_user_id}?effective_at=[&end_date=] |
| EMS configuration | DELETE /communities/{community_id}/energy_management/configs/{config_id}?effective_at=[&end_date=] |
A meter that still has a resource assigned on or after effective_at cannot be ended, and a
resource with ownerships on or after effective_at cannot be ended. Remove those first.
Removing one exact version
To delete a single wrong version without touching the others, use
DELETE /business-validity/managed/{entity_type}/{entity_id}/versions/{version_id}.
entity_type is one of config, membership, meter, resource, ownership,
contract, resp, fixedBilateral or ems. For pairs, join the two IDs with ~:
{resource_id}~{user_id} for ownership and {receiver_user_id}~{provider_user_id} for
fixed bilateral prices. For membership, entity_id is the user_id.
Structural Rules Checked per Period¶
These rules are checked for every day of the requested period, not only for today:
| Rule | When broken |
|---|---|
| A CPE or internal meter can have at most one resource at a time. Use internal meters for more resources behind one CPE (see valid structures) | 409 Conflict |
| A meter cannot have two contracts on the same day | 409 Conflict |
| Total ownership of a resource must not exceed 100% on any day. Below 100% is allowed | Rejected; the error names the first day over 100% |
| A resource version without a meter is allowed, but it is view-only: it is excluded from market, settlement, EMS and sizing until a later version assigns a meter | Not an error |
Effect on Operations¶
Market clearing and settlement¶
Market clearing and settlement run on a set of 15-minute sessions. When the community structure changes inside that set, RECreation automatically splits the run into context windows, each with a constant structure, and runs the market and settlement engines once per window. A change that takes effect on a given day applies from that day's first session (00:00 Lisbon time).
%%{init: {"themeVariables": {"fontSize": "18px"}, "flowchart": {"nodeSpacing": 40, "rankSpacing": 50}}}%%
flowchart TB
S["Run market for Jan 1 to Jan 31<br/>(2,976 sessions)"] --> P{"Structure changes?<br/>Battery: 5 kW until Jan 13,<br/>10 kW from Jan 14"}
P --> W1["Window 1<br/>Jan 1 00:00 to Jan 13 23:45<br/>uses the 5 kW version"]
P --> W2["Window 2<br/>Jan 14 00:00 to Jan 31 23:45<br/>uses the 10 kW version"]
W1 --> E1["Market + settlement<br/>engine run 1"]
W2 --> E2["Market + settlement<br/>engine run 2"]
E1 --> R["Results saved together<br/>(all windows or none)"]
E2 --> R
- New windows also start wherever the requested sessions are not consecutive.
- Results are saved only if every window succeeds. A failure leaves all sessions unchanged.
- Settlement checks that the structure has not changed since market clearing. If it has, the affected sessions are reopened for market clearing instead of being settled.
- A rerun always uses the latest history. If you correct January's structure in March, rerunning January uses the corrected version.
Energy management and sizing¶
EMS optimizations and sizing simulations cannot be split into windows. If the optimization
horizon or sizing period crosses a day on which the structure or the EMS configuration
changes, the request is rejected and the message names the first day of the change
(Operation crosses a business-context boundary on YYYY-MM-DD). For EMS calculations this is
a 409 Conflict. Split the request at that day. If no EMS configuration version is effective
on the target date, the calculation returns 400.
Why these operations are not split
Optimization modules are constantly evolving and still considered to be in development. Multiple context windows will not be supported until their functionality is production-ready.
Historical outputs¶
Billing guides, reports and historical outputs use the member, resource names, meters and ownership shares valid at each interval. An entity that is inactive today still appears for the dates when it was active. Contact details (name, email, address, NIF) always show their current values, because they are not versioned.
Access to History¶
Access is always based on your current permissions, not on the date you query:
- Current admins can read the full history of their community.
- A former member does not regain access to the community by querying a date when they were active.
- Ending a membership does not delete the user account.