Skip to content

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.
  • INACTIVE version: a version covers the day, but with status: 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:

DELETE /core/api/communities/{community_id}/meters/{meter_id}?effective_at=2026-06-01

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: INACTIVE for 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.