Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions alembic/versions/2f6e9d3a1c45_add_weather_data_legacy_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""add weather data legacy model

Revision ID: 2f6e9d3a1c45
Revises: 8ed4b9770721
Create Date: 2026-01-09 09:42:00.000000

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


# revision identifiers, used by Alembic.
revision: str = "2f6e9d3a1c45"
down_revision: Union[str, Sequence[str], None] = "8ed4b9770721"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
bind = op.get_bind()
inspector = sa.inspect(bind)
table_name = "NMA_WeatherData"

if not inspector.has_table(table_name):
op.create_table(
table_name,
sa.Column("LocationId", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("PointID", sa.String(length=10), nullable=False),
sa.Column("WeatherID", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("OBJECTID", sa.Integer(), primary_key=True),
)
return

pk = inspector.get_pk_constraint(table_name)
pk_columns = pk.get("constrained_columns") or []
if pk_columns != ["OBJECTID"]:
op.create_primary_key(
"NMA_WeatherData_pkey",
table_name,
["OBJECTID"],
)


def downgrade() -> None:
"""Downgrade schema."""
op.drop_table("NMA_WeatherData")
17 changes: 17 additions & 0 deletions db/nma_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,21 @@ class SurfaceWaterData(Base):
data_source: Mapped[Optional[str]] = mapped_column("DataSource", String(255))


class WeatherData(Base):
"""
Legacy WeatherData table from AMPAPI.
"""

__tablename__ = "NMA_WeatherData"

location_id: Mapped[Optional[uuid.UUID]] = mapped_column(
"LocationId", UUID(as_uuid=True)
)
point_id: Mapped[str] = mapped_column("PointID", String(10))
weather_id: Mapped[Optional[uuid.UUID]] = mapped_column(
"WeatherID", UUID(as_uuid=True)
)
object_id: Mapped[int] = mapped_column("OBJECTID", Integer, primary_key=True)


# ============= EOF =============================================
194 changes: 194 additions & 0 deletions tests/test_weather_data_legacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# ===============================================================================
# Copyright 2026 ross
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
"""
Unit tests for WeatherData legacy model.

These tests verify the migration of columns from the legacy WeatherData table.
Migrated columns (excluding SSMA_TimeStamp):
- LocationId -> location_id
- PointID -> point_id
- WeatherID -> weather_id
- OBJECTID -> object_id
"""

from uuid import uuid4

from sqlalchemy import func

from db.engine import session_ctx
from db.nma_legacy import WeatherData


def _next_object_id(session) -> int:
max_id = session.query(func.max(WeatherData.object_id)).scalar()
return (max_id or 0) + 1


# ===================== CREATE tests ==========================
def test_create_weather_data_all_fields():
"""Test creating a weather data record with all migrated fields."""
with session_ctx() as session:
record = WeatherData(
object_id=_next_object_id(session),
location_id=uuid4(),
point_id="WX-1001",
weather_id=uuid4(),
)
session.add(record)
session.commit()
session.refresh(record)

assert record.object_id is not None
assert record.point_id == "WX-1001"
assert record.location_id is not None
assert record.weather_id is not None

session.delete(record)
session.commit()


def test_create_weather_data_minimal():
"""Test creating a weather data record with minimal fields."""
with session_ctx() as session:
record = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1002",
)
session.add(record)
session.commit()
session.refresh(record)

assert record.object_id is not None
assert record.point_id == "WX-1002"
assert record.location_id is None
assert record.weather_id is None

session.delete(record)
session.commit()


# ===================== READ tests ==========================
def test_read_weather_data_by_object_id():
"""Test reading a specific weather data record by OBJECTID."""
with session_ctx() as session:
record = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1003",
)
session.add(record)
session.commit()

fetched = session.get(WeatherData, record.object_id)
assert fetched is not None
assert fetched.object_id == record.object_id
assert fetched.point_id == "WX-1003"

session.delete(record)
session.commit()


def test_query_weather_data_by_point_id():
"""Test querying weather data by point_id."""
with session_ctx() as session:
record1 = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1004",
)
record2 = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1005",
)
session.add_all([record1, record2])
session.commit()

results = (
session.query(WeatherData).filter(WeatherData.point_id == "WX-1004").all()
)
assert len(results) >= 1
assert all(r.point_id == "WX-1004" for r in results)

session.delete(record1)
session.delete(record2)
session.commit()


# ===================== UPDATE tests ==========================
def test_update_weather_data():
"""Test updating a weather data record."""
with session_ctx() as session:
record = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1006",
)
session.add(record)
session.commit()

new_location_id = uuid4()
new_weather_id = uuid4()
record.location_id = new_location_id
record.weather_id = new_weather_id
session.commit()
session.refresh(record)

assert record.location_id == new_location_id
assert record.weather_id == new_weather_id

session.delete(record)
session.commit()


# ===================== DELETE tests ==========================
def test_delete_weather_data():
"""Test deleting a weather data record."""
with session_ctx() as session:
record = WeatherData(
object_id=_next_object_id(session),
point_id="WX-1007",
)
session.add(record)
session.commit()

session.delete(record)
session.commit()

fetched = session.get(WeatherData, record.object_id)
assert fetched is None


# ===================== Column existence tests ==========================
def test_weather_data_has_all_migrated_columns():
"""
Test that the model has all expected columns from WeatherData.
"""
expected_columns = [
"location_id",
"point_id",
"weather_id",
"object_id",
]

for column in expected_columns:
assert hasattr(
WeatherData, column
), f"Expected column '{column}' not found in WeatherData model"


def test_weather_data_table_name():
"""Test that the table name follows convention."""
assert WeatherData.__tablename__ == "NMA_WeatherData"


# ============= EOF =============================================
2 changes: 2 additions & 0 deletions transfers/backfill/backfill.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
sys.path.insert(0, str(ROOT))
from transfers.backfill.ngwmn_views import run as run_ngwmn_views
from transfers.backfill.surface_water_data import run as run_surface_water_data
from transfers.backfill.weather_data import run as run_weather_data
from transfers.backfill.waterlevelscontinuous_pressure_daily import (
run as run_pressure_daily,
)
Expand All @@ -44,6 +45,7 @@ def run(batch_size: int = 1000) -> None:
"""
steps = (
("SurfaceWaterData", run_surface_water_data, "BACKFILL_SURFACE_WATER_DATA"),
("WeatherData", run_weather_data, "BACKFILL_WEATHER_DATA"),
(
"Chemistry_SampleInfo",
run_chemistry_sampleinfo,
Expand Down
Loading
Loading