Skip to content

Commit 078c0bd

Browse files
committed
Merge branch 'main' into assign_user_to_client
2 parents de5da90 + 6411448 commit 078c0bd

File tree

9 files changed

+147
-4
lines changed

9 files changed

+147
-4
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.0.53
2+
current_version = 1.0.54
33
commit = True
44
tag = True
55
message = Bump version: {current_version} → {new_version} [ci skip]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Add client-user relationship
2+
3+
Revision ID: 3c765af43b5a
4+
Revises: 81278a3571b2
5+
Create Date: 2025-03-19 13:34:08.803237
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '3c765af43b5a'
14+
down_revision = '81278a3571b2'
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('users', sa.Column('client_uuid', sa.UUID(), nullable=True, comment='The foreign key to the clients table'))
22+
op.create_foreign_key(None, 'users', 'clients', ['client_uuid'], ['client_uuid'])
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade() -> None:
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_constraint(None, 'users', type_='foreignkey')
29+
op.drop_column('users', 'client_uuid')
30+
# ### end Alembic commands ###
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Add probabilistic_values column to forecast value table
2+
3+
Revision ID: 81278a3571b2
4+
Revises: 42f8f283e2c9
5+
Create Date: 2025-03-08 12:57:24.464340
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '81278a3571b2'
14+
down_revision = '42f8f283e2c9'
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('forecast_values', sa.Column('probabilistic_values', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'"), nullable=False, comment='Probabilistic forecast values, like p10, p50, p90'))
22+
# ### end Alembic commands ###
23+
24+
25+
def downgrade() -> None:
26+
# ### commands auto generated by Alembic - please adjust! ###
27+
op.drop_column('forecast_values', 'probabilistic_values')
28+
# ### end Alembic commands ###

pvsite_datamodel/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
UserSQL,
1515
)
1616

17-
__version__ = "1.0.53"
17+
__version__ = "1.0.54"

pvsite_datamodel/sqlmodels.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,17 @@ class UserSQL(Base, CreatedMixin):
5858
nullable=False,
5959
comment="The foreign key to the site_groups table",
6060
)
61+
client_uuid = sa.Column(
62+
UUID(as_uuid=True),
63+
sa.ForeignKey("clients.client_uuid"),
64+
nullable=True,
65+
comment="The foreign key to the clients table",
66+
)
6167

6268
# Relationships
6369
site_group: Mapped["SiteGroupSQL"] = relationship("SiteGroupSQL", back_populates="users")
6470
api_request = relationship("APIRequestSQL", back_populates="user")
71+
client: Mapped[Optional["ClientSQL"]] = relationship("ClientSQL", back_populates="users")
6572

6673

6774
class SiteGroupSQL(Base, CreatedMixin):
@@ -404,6 +411,13 @@ class ForecastValueSQL(Base, CreatedMixin):
404411
comment="The ML Model this forcast value belongs to",
405412
)
406413

414+
probabilistic_values = sa.Column(
415+
JSONB,
416+
nullable=False,
417+
server_default=sa.text("'{}'"), # Default to an empty JSON object
418+
comment="Probabilistic forecast values, like p10, p50, p90",
419+
)
420+
407421
forecast: Mapped["ForecastSQL"] = relationship("ForecastSQL", back_populates="forecast_values")
408422
ml_model: Mapped[Optional[MLModelSQL]] = relationship(
409423
"MLModelSQL", back_populates="forecast_values"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ readme = "README.md"
77

88
[tool.poetry]
99
name = "pvsite-datamodel"
10-
version = "1.0.53"
10+
version = "1.0.54"
1111
description = "SDK for interacting with the PVSite database"
1212
authors = ["Open Climate Fix"]
1313
readme = "README.md"

tests/read/test_get_latest_forecast_values.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ def _add_forecast_value(
1515
horizon_minutes: Optional[int] = None,
1616
created_utc: Optional[dt.datetime] = None,
1717
model_name: Optional[str] = None,
18+
probabilistic_values: Optional[dict] = None,
1819
):
1920
fv = ForecastValueSQL(
2021
forecast_uuid=forecast.forecast_uuid,
2122
forecast_power_kw=power,
2223
start_utc=ts,
2324
end_utc=ts + dt.timedelta(minutes=5),
25+
probabilistic_values=probabilistic_values if probabilistic_values is not None else {},
2426
)
2527
if horizon_minutes:
2628
fv.forecast_horizon_minutes = horizon_minutes
@@ -367,3 +369,64 @@ def test_get_latest_forecast_values_model_name(db_session, sites):
367369
)
368370
assert len(latest_forecast) == 2
369371
assert len(latest_forecast[s1]) == 0
372+
373+
374+
def test_get_latest_forecast_values_probabilistic_value_limit2(db_session, sites):
375+
# Retrieve two sites for testing (limit(2))
376+
site_uuids = [site.site_uuid for site in db_session.query(SiteSQL.site_uuid).limit(2)]
377+
site1, site2 = site_uuids
378+
379+
forecast_version = "123"
380+
381+
# Create a forecast for each site
382+
forecast1 = ForecastSQL(
383+
site_uuid=site1,
384+
forecast_version=forecast_version,
385+
timestamp_utc=dt.datetime(2000, 1, 1),
386+
)
387+
forecast2 = ForecastSQL(
388+
site_uuid=site2,
389+
forecast_version=forecast_version,
390+
timestamp_utc=dt.datetime(2000, 1, 1),
391+
)
392+
db_session.add_all([forecast1, forecast2])
393+
db_session.commit()
394+
395+
d0 = dt.datetime(2000, 1, 1, 0, tzinfo=dt.timezone.utc)
396+
397+
# For site1: Add a forecast value with explicit probabilistic_values provided
398+
prob_values = {"p10": 10, "p50": 50, "p90": 90}
399+
_add_forecast_value(
400+
db_session, forecast1, power=1.0, ts=d0, horizon_minutes=0, probabilistic_values=prob_values
401+
)
402+
403+
# For site2: Add a forecast value without providing probabilistic_values (defaults to {})
404+
_add_forecast_value(
405+
db_session,
406+
forecast2,
407+
power=2.0,
408+
ts=d0,
409+
horizon_minutes=0,
410+
)
411+
412+
db_session.commit()
413+
414+
# Retrieve the latest forecast values starting from d0 for both sites
415+
latest_forecast = get_latest_forecast_values_by_site(db_session, site_uuids, d0)
416+
417+
# Verify that forecast values exist for both sites
418+
assert site1 in latest_forecast
419+
assert site2 in latest_forecast
420+
# Expect one forecast value for each site
421+
assert len(latest_forecast[site1]) == 1
422+
assert len(latest_forecast[site2]) == 1
423+
424+
# Retrieve the forecast values for each site
425+
fv_site1 = latest_forecast[site1][0]
426+
fv_site2 = latest_forecast[site2][0]
427+
428+
# Assert that site1's forecast value has the explicit probabilistic values provided
429+
assert fv_site1.probabilistic_values == prob_values
430+
431+
# Assert that site2's forecast value uses the default (empty dictionary)
432+
assert fv_site2.probabilistic_values == {}

tests/write/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def test_assign_site_to_client(db_session):
4444
def test_assign_user_to_client(db_session):
4545
"""Test to assign a user to a client"""
4646
user = create_user(
47-
session=db_session, user_uuid="123e4567-e89b-12d3-a456-426614174000", user_name="Test User"
47+
session=db_session,
48+
email="test_user@ocf.org",
4849
)
4950

5051
client = create_client(session=db_session, client_name="Test Client")

tests/write/test_forecast.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def test_insert_forecast_for_existing_site(self, db_session, forecast_valid_inpu
3535
assert ptypes.is_datetime64_any_dtype(forecast_values_df["end_utc"])
3636
assert ptypes.is_numeric_dtype(forecast_values_df["forecast_power_kw"])
3737
assert ptypes.is_numeric_dtype(forecast_values_df["horizon_minutes"])
38+
if "probabilistic_values" in forecast_values_df.columns:
39+
assert forecast_values_df["probabilistic_values"].dtype == object
3840

3941
insert_forecast_values(
4042
db_session,
@@ -47,6 +49,11 @@ def test_insert_forecast_for_existing_site(self, db_session, forecast_valid_inpu
4749
assert db_session.query(ForecastSQL).count() == 1
4850
assert db_session.query(ForecastValueSQL).count() == 10
4951

52+
for fv in db_session.query(ForecastValueSQL).all():
53+
assert isinstance(fv.probabilistic_values, dict)
54+
if "probabilistic_values" not in forecast_values_df.columns:
55+
assert fv.probabilistic_values == {}
56+
5057
def test_invalid_forecast_meta(self, db_session, forecast_with_invalid_meta_input):
5158
"""Test function errors on invalid forecast metadata"""
5259
forecast_meta, forecast_values = forecast_with_invalid_meta_input

0 commit comments

Comments
 (0)