Skip to content

Commit b9127e2

Browse files
dfcoffinclaude
andauthored
feat: ESPI 4.0 Schema Compliance - Phase 3: IntervalReading (#68)
This commit implements Phase 3 of ESPI 4.0 schema compliance, updating IntervalReading to match the ESPI 4.0 XSD specification (espi.xsd:1016). Key Changes: IntervalReadingEntity: - Changed ID from UUID to Long (48+ bits) per Object pattern - IntervalReading extends Object, not IdentifiedObject - Reordered fields to match XSD sequence: cost → readingQualities → timePeriod → value → consumptionTier → tou → cpp - Updated @GeneratedValue to use IDENTITY strategy - Removed UUID-related imports IntervalReadingDto: - Removed currency field (not in ESPI 4.0 XSD) - Reordered record components to match XSD sequence - Updated @XmlType propOrder for correct XML marshalling - Updated all convenience constructors IntervalReadingMapper: - Simplified mappings (MapStruct handles by name) - Removed explicit field mappings - Kept only necessary ignore mappings (id, intervalBlock) Flyway Migrations: - Moved meter_readings, interval_blocks, and interval_readings tables from V3 to V2 vendor-specific files - Rationale: IntervalReading requires vendor-specific auto-increment syntax (BIGINT AUTO_INCREMENT for MySQL/H2, BIGSERIAL for PostgreSQL) - Updated V3 reading_qualities.interval_reading_id from CHAR(36) to BIGINT - Added clear documentation in V3 explaining table relocation Test Updates: - Fixed DtoExportServiceImplTest to use new constructor signature - All 550 tests passing including integration tests Migration Strategy: - V1: Base vendor-neutral tables - V2: Vendor-specific tables (now includes meter_readings, interval_blocks, interval_readings with correct auto-increment syntax) - V3: Additional tables that depend on V1 and V2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 <[email protected]>
1 parent 8ecad71 commit b9127e2

File tree

8 files changed

+426
-185
lines changed

8 files changed

+426
-185
lines changed

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,11 @@
2525
import lombok.Setter;
2626
import org.greenbuttonalliance.espi.common.domain.common.DateTimeInterval;
2727
import org.hibernate.annotations.BatchSize;
28-
import org.hibernate.annotations.JdbcTypeCode;
2928
import org.hibernate.proxy.HibernateProxy;
30-
import org.hibernate.type.SqlTypes;
3129

3230
import java.util.ArrayList;
3331
import java.util.List;
3432
import java.util.Objects;
35-
import java.util.UUID;
3633

3734
/**
3835
* Pure JPA/Hibernate entity for IntervalReading without JAXB concerns.
@@ -52,13 +49,13 @@
5249
public class IntervalReadingEntity {
5350

5451
/**
55-
* Primary key identifier.
52+
* Primary key identifier (48+ bits as per ESPI requirement).
53+
* IntervalReading extends Object (not IdentifiedObject) per ESPI 4.0 XSD.
5654
*/
5755
@Id
58-
@GeneratedValue(strategy = GenerationType.UUID)
59-
@JdbcTypeCode(SqlTypes.CHAR)
60-
@Column(length = 36, columnDefinition = "char(36)", updatable = false, nullable = false)
61-
private UUID id;
56+
@GeneratedValue(strategy = GenerationType.IDENTITY)
57+
@Column(name = "id")
58+
private Long id;
6259

6360
/**
6461
* Cost associated with this interval reading.
@@ -67,6 +64,29 @@ public class IntervalReadingEntity {
6764
@Column(name = "cost")
6865
private Long cost;
6966

67+
/**
68+
* Reading quality indicators for this interval reading.
69+
* One-to-many relationship with cascade and orphan removal.
70+
* -- GETTER --
71+
* Gets the reading qualities collection.
72+
* Lombok @Data should generate this, but added manually for compilation.
73+
*
74+
* @return the list of reading qualities
75+
76+
*/
77+
@OneToMany(mappedBy = "intervalReading", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
78+
@BatchSize(size = 10)
79+
private List<ReadingQualityEntity> readingQualities = new ArrayList<>();
80+
81+
/**
82+
* Time period for this interval reading.
83+
* Embedded value object containing start time and duration.
84+
*/
85+
@Embedded
86+
@AttributeOverride(name = "start", column = @Column(name = "time_period_start"))
87+
@AttributeOverride(name = "duration", column = @Column(name = "time_period_duration"))
88+
private DateTimeInterval timePeriod;
89+
7090
/**
7191
* The actual measured value for this interval.
7292
* This is the primary measurement data.
@@ -95,15 +115,6 @@ public class IntervalReadingEntity {
95115
@Column(name = "cpp")
96116
private Long cpp;
97117

98-
/**
99-
* Time period for this interval reading.
100-
* Embedded value object containing start time and duration.
101-
*/
102-
@Embedded
103-
@AttributeOverride(name = "start", column = @Column(name = "time_period_start"))
104-
@AttributeOverride(name = "duration", column = @Column(name = "time_period_duration"))
105-
private DateTimeInterval timePeriod;
106-
107118
/**
108119
* Interval block that contains this reading.
109120
* Many interval readings belong to one interval block.
@@ -112,20 +123,6 @@ public class IntervalReadingEntity {
112123
@JoinColumn(name = "interval_block_id")
113124
private IntervalBlockEntity intervalBlock;
114125

115-
/**
116-
* Reading quality indicators for this interval reading.
117-
* One-to-many relationship with cascade and orphan removal.
118-
* -- GETTER --
119-
* Gets the reading qualities collection.
120-
* Lombok @Data should generate this, but added manually for compilation.
121-
*
122-
* @return the list of reading qualities
123-
124-
*/
125-
@OneToMany(mappedBy = "intervalReading", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
126-
@BatchSize(size = 10)
127-
private List<ReadingQualityEntity> readingQualities = new ArrayList<>();
128-
129126
/**
130127
* Constructor with core reading data.
131128
*
@@ -333,10 +330,10 @@ public String toString() {
333330
return getClass().getSimpleName() + "(" +
334331
"id = " + getId() + ", " +
335332
"cost = " + getCost() + ", " +
333+
"timePeriod = " + getTimePeriod() + ", " +
336334
"value = " + getValue() + ", " +
337335
"consumptionTier = " + getConsumptionTier() + ", " +
338336
"tou = " + getTou() + ", " +
339-
"cpp = " + getCpp() + ", " +
340-
"timePeriod = " + getTimePeriod() + ")";
337+
"cpp = " + getCpp() + ")";
341338
}
342339
}

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,21 @@
3535
@XmlRootElement(name = "IntervalReading", namespace = "http://naesb.org/espi")
3636
@XmlAccessorType(XmlAccessType.FIELD)
3737
@XmlType(name = "IntervalReading", namespace = "http://naesb.org/espi", propOrder = {
38-
"cost", "currency", "value", "timePeriod", "readingQualities",
39-
"consumptionTier", "tou", "cpp"
38+
"cost", "readingQualities", "timePeriod", "value", "consumptionTier", "tou", "cpp"
4039
})
4140
public record IntervalReadingDto(
4241

4342
@XmlElement(name = "cost")
4443
Long cost,
4544

46-
@XmlElement(name = "currency")
47-
Integer currency,
48-
49-
@XmlElement(name = "value")
50-
Long value,
45+
@XmlElement(name = "ReadingQuality")
46+
List<ReadingQualityDto> readingQualities,
5147

5248
@XmlElement(name = "timePeriod")
5349
DateTimeIntervalDto timePeriod,
5450

55-
@XmlElement(name = "ReadingQuality")
56-
//@XmlElementWrapper(name = "ReadingQualities")
57-
List<ReadingQualityDto> readingQualities,
51+
@XmlElement(name = "value")
52+
Long value,
5853

5954
@XmlElement(name = "consumptionTier")
6055
Integer consumptionTier,
@@ -70,20 +65,20 @@ public record IntervalReadingDto(
7065
* Default constructor for JAXB.
7166
*/
7267
public IntervalReadingDto() {
73-
this(null, null, null, null, null, null, null, null);
68+
this(null, null, null, null, null, null, null);
7469
}
7570

7671
/**
7772
* Minimal constructor for basic interval reading data.
7873
*/
7974
public IntervalReadingDto(Long value, DateTimeIntervalDto timePeriod) {
80-
this(null, null, value, timePeriod, null, null, null, null);
75+
this(null, null, timePeriod, value, null, null, null);
8176
}
8277

8378
/**
8479
* Constructor for interval reading with cost information.
8580
*/
86-
public IntervalReadingDto(Long value, Long cost, Integer currency, DateTimeIntervalDto timePeriod) {
87-
this(cost, currency, value, timePeriod, null, null, null, null);
81+
public IntervalReadingDto(Long value, Long cost, DateTimeIntervalDto timePeriod) {
82+
this(cost, null, timePeriod, value, null, null, null);
8883
}
8984
}

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,17 @@ public interface IntervalReadingMapper {
4444
* @param entity the interval reading entity
4545
* @return the interval reading DTO
4646
*/
47-
@Mapping(target = "cost", source = "cost")
48-
@Mapping(target = "currency", ignore = true)
49-
@Mapping(target = "value", source = "value")
50-
@Mapping(target = "timePeriod", source = "timePeriod")
51-
@Mapping(target = "readingQualities", source = "readingQualities")
52-
@Mapping(target = "consumptionTier", source = "consumptionTier")
53-
@Mapping(target = "tou", source = "tou")
54-
@Mapping(target = "cpp", source = "cpp")
5547
IntervalReadingDto toDto(IntervalReadingEntity entity);
5648

5749
/**
5850
* Converts an IntervalReadingDto to an IntervalReadingEntity.
5951
* Maps all related DTOs to their corresponding entities.
52+
* The ID is generated by the database, and intervalBlock is set by the parent.
6053
*
6154
* @param dto the interval reading DTO
6255
* @return the interval reading entity
6356
*/
6457
@Mapping(target = "id", ignore = true)
65-
@Mapping(target = "cost", source = "cost")
66-
@Mapping(target = "value", source = "value")
67-
@Mapping(target = "timePeriod", source = "timePeriod")
68-
@Mapping(target = "readingQualities", source = "readingQualities")
69-
@Mapping(target = "consumptionTier", source = "consumptionTier")
70-
@Mapping(target = "tou", source = "tou")
71-
@Mapping(target = "cpp", source = "cpp")
7258
@Mapping(target = "intervalBlock", ignore = true)
7359
IntervalReadingEntity toEntity(IntervalReadingDto dto);
7460

openespi-common/src/main/resources/db/migration/V3__Create_additiional_Base_Tables.sql

Lines changed: 56 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,57 @@
1-
-- Meter Reading Table
2-
CREATE TABLE meter_readings
3-
(
4-
id CHAR(36) PRIMARY KEY ,
5-
description VARCHAR(255),
6-
created TIMESTAMP NOT NULL,
7-
updated TIMESTAMP NOT NULL,
8-
published TIMESTAMP,
9-
up_link_rel VARCHAR(255),
10-
up_link_href VARCHAR(1024),
11-
up_link_type VARCHAR(255),
12-
self_link_rel VARCHAR(255),
13-
self_link_href VARCHAR(1024),
14-
self_link_type VARCHAR(255),
15-
16-
-- Foreign key relationships
17-
usage_point_id CHAR(36),
18-
reading_type_id CHAR(36),
19-
20-
FOREIGN KEY (usage_point_id) REFERENCES usage_points (id) ON DELETE CASCADE,
21-
FOREIGN KEY (reading_type_id) REFERENCES reading_types (id) ON DELETE SET NULL
22-
);
23-
24-
-- Indexes for meter_readings table
25-
CREATE INDEX idx_meter_reading_usage_point_id ON meter_readings (usage_point_id);
26-
CREATE INDEX idx_meter_reading_reading_type_id ON meter_readings (reading_type_id);
27-
CREATE INDEX idx_meter_reading_created ON meter_readings (created);
28-
CREATE INDEX idx_meter_reading_updated ON meter_readings (updated);
29-
30-
-- Related Links Table for Meter Readings
31-
CREATE TABLE meter_reading_related_links
32-
(
33-
meter_reading_id CHAR(36) NOT NULL,
34-
related_links VARCHAR(1024),
35-
FOREIGN KEY (meter_reading_id) REFERENCES meter_readings (id) ON DELETE CASCADE
36-
);
37-
38-
-- Indexes for meter_reading_related_links table
39-
CREATE INDEX idx_meter_reading_related_links ON meter_reading_related_links (meter_reading_id);
40-
41-
-- Interval Block Table
42-
CREATE TABLE interval_blocks
43-
(
44-
id CHAR(36) PRIMARY KEY ,
45-
description VARCHAR(255),
46-
created TIMESTAMP NOT NULL,
47-
updated TIMESTAMP NOT NULL,
48-
published TIMESTAMP,
49-
up_link_rel VARCHAR(255),
50-
up_link_href VARCHAR(1024),
51-
up_link_type VARCHAR(255),
52-
self_link_rel VARCHAR(255),
53-
self_link_href VARCHAR(1024),
54-
self_link_type VARCHAR(255),
55-
56-
-- Interval block specific fields
57-
interval_duration BIGINT,
58-
interval_start BIGINT,
59-
60-
-- Foreign key relationships
61-
meter_reading_id CHAR(36),
62-
63-
FOREIGN KEY (meter_reading_id) REFERENCES meter_readings (id) ON DELETE CASCADE
64-
);
65-
66-
-- Indexes for interval_blocks table
67-
CREATE INDEX idx_interval_block_meter_reading_id ON interval_blocks (meter_reading_id);
68-
CREATE INDEX idx_interval_block_start ON interval_blocks (interval_start);
69-
CREATE INDEX idx_interval_block_created ON interval_blocks (created);
70-
CREATE INDEX idx_interval_block_updated ON interval_blocks (updated);
71-
72-
-- Related Links Table for Interval Blocks
73-
CREATE TABLE interval_block_related_links
74-
(
75-
interval_block_id CHAR(36) NOT NULL,
76-
related_links VARCHAR(1024),
77-
FOREIGN KEY (interval_block_id) REFERENCES interval_blocks (id) ON DELETE CASCADE
78-
);
79-
80-
-- Indexes for interval_block_related_links table
81-
CREATE INDEX idx_interval_block_related_links ON interval_block_related_links (interval_block_id);
82-
83-
-- Interval Reading Table
84-
CREATE TABLE interval_readings
85-
(
86-
id CHAR(36) PRIMARY KEY ,
87-
description VARCHAR(255),
88-
created TIMESTAMP,
89-
updated TIMESTAMP,
90-
published TIMESTAMP,
91-
up_link_rel VARCHAR(255),
92-
up_link_href VARCHAR(1024),
93-
up_link_type VARCHAR(255),
94-
self_link_rel VARCHAR(255),
95-
self_link_href VARCHAR(1024),
96-
self_link_type VARCHAR(255),
97-
98-
-- Interval reading specific fields
99-
cost BIGINT,
100-
reading_value BIGINT,
101-
time_period_start BIGINT,
102-
time_period_duration BIGINT,
103-
consumption_tier BIGINT,
104-
tou BIGINT,
105-
cpp BIGINT,
106-
107-
-- Foreign key relationships
108-
interval_block_id CHAR(36),
109-
110-
FOREIGN KEY (interval_block_id) REFERENCES interval_blocks (id) ON DELETE CASCADE
111-
);
112-
113-
-- Indexes for interval_readings table
114-
CREATE INDEX idx_interval_reading_interval_block_id ON interval_readings (interval_block_id);
115-
CREATE INDEX idx_interval_reading_time_period_start ON interval_readings (time_period_start);
116-
CREATE INDEX idx_interval_reading_value ON interval_readings (reading_value);
117-
CREATE INDEX idx_interval_reading_created ON interval_readings (created);
118-
CREATE INDEX idx_interval_reading_updated ON interval_readings (updated);
1+
/*
2+
* OpenESPI Additional Base Tables Migration
3+
*
4+
* Copyright (c) 2018-2025 Green Button Alliance, Inc.
5+
* Licensed under the Apache License, Version 2.0
6+
*
7+
* This migration creates additional vendor-neutral tables that depend on both
8+
* V1 (base tables) and V2 (vendor-specific tables) to already exist.
9+
*
10+
* IMPORTANT: The following tables were moved to V2 vendor-specific files
11+
* because they require vendor-specific syntax OR are part of dependency chains
12+
* that include vendor-specific tables:
13+
*
14+
* Moved to V2:
15+
* - meter_readings (depends on usage_points from V2)
16+
* - meter_reading_related_links (FK dependency)
17+
* - interval_blocks (depends on meter_readings)
18+
* - interval_block_related_links (FK dependency)
19+
* - interval_readings (extends Object, requires vendor-specific auto-increment, depends on interval_blocks)
20+
*
21+
* Reason: IntervalReading extends Object (not IdentifiedObject) per ESPI 4.0 XSD (espi.xsd:1016),
22+
* requiring Long ID with vendor-specific auto-increment syntax (BIGINT AUTO_INCREMENT for MySQL/H2,
23+
* BIGSERIAL for PostgreSQL). To keep the dependency chain together (meter_readings → interval_blocks
24+
* → interval_readings), all three were moved to V2 vendor files.
25+
*
26+
* Tables in this migration:
27+
* - reading_qualities (depends on interval_readings from V2)
28+
* - usage_summaries (depends on usage_points from V2)
29+
* - usage_summary_related_links (FK dependency)
30+
* - subscription_usage_points (join table)
31+
* - aggregated_node_refs (depends on pnode_refs from V2)
32+
* - customer schema tables
33+
* - end_device schema tables
34+
* - service location/supplier tables
35+
* - statement tables
36+
*
37+
* Compatible with: H2, MySQL, PostgreSQL
38+
*/
39+
40+
-- ==================================================================================
41+
-- TABLES MOVED TO V2 VENDOR-SPECIFIC MIGRATION FILES
42+
-- ==================================================================================
43+
-- The following tables have been moved to V2 vendor-specific files:
44+
-- - db/vendor/mysql/V2__MySQL_Specific_Tables.sql
45+
-- - db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql
46+
-- - db/vendor/h2/V2__H2_Specific_Tables.sql
47+
--
48+
-- Tables moved:
49+
-- 1. meter_readings + meter_reading_related_links
50+
-- 2. interval_blocks + interval_block_related_links
51+
-- 3. interval_readings (no related_links - extends Object, not IdentifiedObject)
52+
--
53+
-- See file header for detailed explanation.
54+
-- ==================================================================================
11955

12056
-- Reading Quality Table
12157
CREATE TABLE reading_qualities
@@ -136,7 +72,8 @@ CREATE TABLE reading_qualities
13672
quality VARCHAR(50),
13773

13874
-- Foreign key relationships
139-
interval_reading_id CHAR(36),
75+
-- IntervalReading uses Long ID (BIGINT/BIGSERIAL) as it extends Object, not IdentifiedObject
76+
interval_reading_id BIGINT,
14077

14178
FOREIGN KEY (interval_reading_id) REFERENCES interval_readings (id) ON DELETE CASCADE
14279
);

0 commit comments

Comments
 (0)