Skip to content

Commit 71217ad

Browse files
committed
Added dynamic version to DefaultDERControl
1 parent f2fffa6 commit 71217ad

File tree

10 files changed

+88
-19
lines changed

10 files changed

+88
-19
lines changed

src/envoy/admin/manager/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def update_site_control_default(session: AsyncSession, site_id: int, reque
110110
return False
111111

112112
if site.default_site_control is None:
113-
site.default_site_control = DefaultSiteControl(changed_time=now, site_id=site.site_id)
113+
site.default_site_control = DefaultSiteControl(changed_time=now, site_id=site.site_id, version=0)
114114
else:
115115
site.default_site_control.changed_time = now
116116

@@ -134,6 +134,7 @@ async def update_site_control_default(session: AsyncSession, site_id: int, reque
134134
)
135135
site.default_site_control.ramp_rate_percent_per_second = ramp_rate_value
136136

137+
site.default_site_control.version = site.default_site_control.version + 1
137138
await session.commit()
138139

139140
await NotificationManager.notify_changed_deleted_entities(SubscriptionResource.DEFAULT_SITE_CONTROL, now)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""added_defaultsitecontrol_version
2+
3+
Revision ID: 29a83be2701e
4+
Revises: 8ca240a5d8f3
5+
Create Date: 2025-12-01 15:14:36.070394
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "29a83be2701e"
14+
down_revision = "8ca240a5d8f3"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column(
22+
"archive_default_site_control", sa.Column("version", sa.INTEGER(), server_default="0", nullable=False)
23+
)
24+
op.add_column("default_site_control", sa.Column("version", sa.INTEGER(), server_default="0", nullable=False))
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade() -> None:
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.drop_column("default_site_control", "version")
31+
op.drop_column("archive_default_site_control", "version")
32+
# ### end Alembic commands ###

src/envoy/server/manager/derp.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def _prefer_left(left: Any, right: Any) -> Any:
258258

259259
if default_site_control is not None:
260260
return DefaultSiteControl(
261+
version=default_site_control.version,
261262
import_limit_active_watts=_prefer_left(
262263
default_site_control.import_limit_active_watts, default_doe.import_limit_active_watts
263264
),
@@ -275,6 +276,7 @@ def _prefer_left(left: Any, right: Any) -> Any:
275276
),
276277
)
277278
return DefaultSiteControl(
279+
version=0,
278280
import_limit_active_watts=default_doe.import_limit_active_watts,
279281
export_limit_active_watts=default_doe.export_limit_active_watts,
280282
generation_limit_active_watts=default_doe.generation_limit_active_watts,

src/envoy/server/mapper/csip_aus/doe.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def map_to_default_response(
161161

162162
return DefaultDERControl(
163163
href=DERControlMapper.default_control_href(scope, display_site_id, der_program_id),
164+
version=default_doe.version,
164165
subscribable=SubscribableType.resource_supports_non_conditional_subscriptions,
165166
mRID=MridMapper.encode_default_doe_mrid(scope),
166167
setGradW=default_doe.ramp_rate_percent_per_second,

src/envoy/server/model/archive/site.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ class ArchiveDefaultSiteControl(ArchiveBase):
225225
created_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
226226
changed_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
227227

228+
version: Mapped[int] = mapped_column(INTEGER, server_default="0") # Incremented whenever this record is changed
229+
228230
import_limit_active_watts: Mapped[Optional[Decimal]] = mapped_column(
229231
DECIMAL(16, original_models.site.DOE_DECIMAL_PLACES), nullable=True
230232
) # Constraint on imported active power

src/envoy/server/model/site.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ class DefaultSiteControl(Base):
401401
) # When this record was created
402402
changed_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
403403

404+
version: Mapped[int] = mapped_column(INTEGER, server_default="0") # Incremented whenever this record is changed
405+
404406
import_limit_active_watts: Mapped[Optional[Decimal]] = mapped_column(
405407
DECIMAL(16, DOE_DECIMAL_PLACES), nullable=True
406408
) # Constraint on imported active power

tests/integration/admin/test_config.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
)
1818
from envoy_schema.admin.schema.uri import ServerConfigRuntimeUri, SiteControlDefaultConfigUri
1919
from httpx import AsyncClient
20-
from sqlalchemy import delete
20+
from sqlalchemy import delete, select
2121

2222
from envoy.server.model.server import RuntimeServerConfig
23+
from envoy.server.model.site import DefaultSiteControl
2324
from tests.integration.response import read_response_body_string
2425

2526

@@ -112,8 +113,19 @@ async def test_get_update_server_config(admin_client_auth: AsyncClient, pg_base_
112113
)
113114
@pytest.mark.anyio
114115
async def test_get_and_update_site_control_default(
115-
admin_client_auth: AsyncClient, site_id: int, expected: Optional[tuple]
116+
pg_base_config,
117+
admin_client_auth: AsyncClient,
118+
site_id: int,
119+
expected: Optional[tuple],
116120
):
121+
version_before = 0
122+
async with generate_async_session(pg_base_config) as session:
123+
db_record = (
124+
await session.execute(select(DefaultSiteControl).where(DefaultSiteControl.site_id == site_id))
125+
).scalar_one_or_none()
126+
if db_record:
127+
version_before = db_record.version
128+
117129
resp = await admin_client_auth.get(SiteControlDefaultConfigUri.format(site_id=site_id))
118130
if expected is None:
119131
assert resp.status_code == HTTPStatus.NOT_FOUND
@@ -161,3 +173,10 @@ async def test_get_and_update_site_control_default(
161173
config.server_default_generation_limit_watts,
162174
config.server_default_load_limit_watts,
163175
)
176+
177+
# Version number in the DB should be getting updated
178+
async with generate_async_session(pg_base_config) as session:
179+
db_record = (
180+
await session.execute(select(DefaultSiteControl).where(DefaultSiteControl.site_id == site_id))
181+
).scalar_one()
182+
assert db_record.version == version_before + 1, "The version field should be updated per update"

tests/unit/admin/manager/test_config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ async def test_update_site_control_default_all_vals_update(
4444
control_request: ControlDefaultRequest,
4545
):
4646
"""Tests that the values for existing/new control defaults can be correctly updated"""
47+
async with generate_async_session(pg_base_config) as session:
48+
version_before = (
49+
await session.execute(select(DefaultSiteControl.version).where(DefaultSiteControl.site_id == site_id))
50+
).scalar_one()
51+
4752
async with generate_async_session(pg_base_config) as session:
4853
await ConfigManager.update_site_control_default(session, site_id, control_request)
4954

@@ -56,5 +61,6 @@ async def test_update_site_control_default_all_vals_update(
5661
assert saved_result.generation_limit_active_watts == control_request.generation_limit_watts.value
5762
assert saved_result.load_limit_active_watts == control_request.load_limit_watts.value
5863
assert saved_result.ramp_rate_percent_per_second == control_request.ramp_rate_percent_per_second.value
64+
assert saved_result.version == version_before + 1, "This should be incremented as part of the update"
5965

6066
mock_notify_changed_deleted_entities.assert_called_once()

tests/unit/server/manager/test_derp.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -628,24 +628,24 @@ async def test_fetch_default_doe_controls_for_site_no_global_default(
628628
(None, None, None),
629629
(
630630
DefaultDoeConfiguration(100, 200, 300, 400, 50),
631-
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0),
632-
(0, 200, 300, 0, 50),
631+
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0, version=999),
632+
(999, 0, 200, 300, 0, 50),
633633
),
634634
(
635635
DefaultDoeConfiguration(None, 200, 300, None, 50),
636-
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0),
637-
(0, 200, 300, 0, 50),
636+
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0, version=888),
637+
(888, 0, 200, 300, 0, 50),
638638
),
639639
(
640640
DefaultDoeConfiguration(None, None, None, None, None),
641-
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0),
642-
(0, None, None, 0, None),
641+
DefaultSiteControl(import_limit_active_watts=0, load_limit_active_watts=0, version=777),
642+
(777, 0, None, None, 0, None),
643643
),
644644
# No site control
645645
(
646646
DefaultDoeConfiguration(100, 200, 300, 400, 50),
647647
None,
648-
(100, 200, 300, 400, 50),
648+
(0, 100, 200, 300, 400, 50),
649649
),
650650
# Partial site control
651651
(
@@ -656,8 +656,8 @@ async def test_fetch_default_doe_controls_for_site_no_global_default(
656656
load_limit_active_watts=400,
657657
ramp_rate_percent_per_second=50,
658658
),
659-
DefaultSiteControl(import_limit_active_watts=111, load_limit_active_watts=444),
660-
(111, 200, 300, 444, 50),
659+
DefaultSiteControl(import_limit_active_watts=111, load_limit_active_watts=444, version=555),
660+
(555, 111, 200, 300, 444, 50),
661661
),
662662
# Full site control
663663
(
@@ -674,8 +674,9 @@ async def test_fetch_default_doe_controls_for_site_no_global_default(
674674
generation_limit_active_watts=3,
675675
load_limit_active_watts=4,
676676
ramp_rate_percent_per_second=5,
677+
version=6,
677678
),
678-
(1, 2, 3, 4, 5),
679+
(6, 1, 2, 3, 4, 5),
679680
),
680681
(
681682
None,
@@ -685,8 +686,9 @@ async def test_fetch_default_doe_controls_for_site_no_global_default(
685686
generation_limit_active_watts=3,
686687
load_limit_active_watts=4,
687688
ramp_rate_percent_per_second=5,
689+
version=7,
688690
),
689-
(1, 2, 3, 4, 5),
691+
(7, 1, 2, 3, 4, 5),
690692
),
691693
],
692694
)
@@ -697,8 +699,9 @@ def test_resolve_default_site_control(default_doe_config, default_site_control,
697699
if expected is None:
698700
assert result is None
699701
else:
700-
assert result.import_limit_active_watts == expected[0]
701-
assert result.export_limit_active_watts == expected[1]
702-
assert result.generation_limit_active_watts == expected[2]
703-
assert result.load_limit_active_watts == expected[3]
704-
assert result.ramp_rate_percent_per_second == expected[4]
702+
assert result.version == expected[0]
703+
assert result.import_limit_active_watts == expected[1]
704+
assert result.export_limit_active_watts == expected[2]
705+
assert result.generation_limit_active_watts == expected[3]
706+
assert result.load_limit_active_watts == expected[4]
707+
assert result.ramp_rate_percent_per_second == expected[5]

tests/unit/server/mapper/csip_aus/test_doe.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def test_map_default_to_response(optional_is_none: bool):
177177
assert result.href.startswith("/my/prefix/")
178178
assert f"/{site_id}/" in result.href
179179
assert f"/{derp_id}/" in result.href
180+
assert result.version == doe_default.version
180181

181182
if doe_default.export_limit_active_watts is None:
182183
assert result.DERControlBase_.opModExpLimW is None

0 commit comments

Comments
 (0)