Skip to content
Open
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
165 changes: 165 additions & 0 deletions mapcat/alembic/versions/0762b3c7694d_swapping_to_datetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
"""Swapping to datetime

Revision ID: 0762b3c7694d
Revises: 46575bc0d660
Create Date: 2026-07-27 11:19:49.803412

"""

from __future__ import annotations

from collections.abc import Sequence
from datetime import datetime, timezone

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "0762b3c7694d"
down_revision: str | None = "46575bc0d660"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None

TABLE_DEFINITIONS = {
"atomic_maps": {
"primary_key_name": "atomic_map_id",
"column_names": [("ctime", False)],
},
"atomic_map_coadds": {
"primary_key_name": "coadd_id",
"column_names": [("start_time", False), ("stop_time", False)],
},
"depth_one_maps": {
"primary_key_name": "map_id",
"column_names": [("ctime", True), ("start_time", True), ("stop_time", True)],
},
"depth_one_coadds": {
"primary_key_name": "coadd_id",
"column_names": [("ctime", False), ("start_time", False), ("stop_time", False)],
},
}


def unix_to_datetime(
table_name: str,
primary_key_name: str,
column_names: list[tuple[str, bool]],
) -> None:
"""
Convert a column from unix time to datetime.

Parameters
----------
table_name : str
The name of the table to modify.
primary_key_name : str
The name of the primary key column for the table.
column_names : list[tuple[str, bool]]
The names of the columns to convert and boolean defining whether the column is indexed.
"""
bind = op.get_bind()
metadata = sa.MetaData()
cur_table = sa.Table(table_name, metadata, autoload_with=bind)
for column_name, is_indexed in column_names:
temp_col_name = f"temp_datetime_{column_name}"
if is_indexed:
op.drop_index(f"ix_{table_name}_{column_name}", table_name=table_name)

op.add_column(
table_name, sa.Column(temp_col_name, sa.DateTime(), nullable=True)
)

stmt = sa.select(cur_table.c[column_name], cur_table.c[primary_key_name])
results = bind.execute(stmt).fetchall()
for row in results:
unix_time = row[column_name]
primary_key_value = row[primary_key_name]
datetime_value = datetime.fromtimestamp(int(unix_time), tz=timezone.utc)
update_stmt = (
cur_table.update()
.where(cur_table.c[primary_key_name] == primary_key_value)
.values({temp_col_name: datetime_value})
)
bind.execute(update_stmt)

with op.batch_alter_table(table_name) as batch_op:
batch_op.drop_column(column_name)
batch_op.alter_column(
temp_col_name,
new_column_name=column_name,
nullable=False,
)

if is_indexed:
op.create_index(f"ix_{table_name}_{column_name}", table_name, [column_name])


def _datetime_to_unix_value(datetime_value: datetime) -> int | None:
datetime_value = datetime_value.astimezone(timezone.utc)
return int(datetime_value.timestamp())


def datetime_to_unix(
table_name: str,
primary_key_name: str,
column_names: list[tuple[str, bool]],
) -> None:
"""
Convert a column from datetime to unix time.

Parameters
----------
table_name : str
The name of the table to modify.
primary_key_name : str
The name of the primary key column for the table.
column_names : list[tuple[str, bool]]
The names of the columns to convert and a boolean defining whether the column is indexed.
"""
bind = op.get_bind()
metadata = sa.MetaData()
cur_table = sa.Table(table_name, metadata, autoload_with=bind)
for column_name, is_indexed in column_names:
temp_col_name = f"temp_unix_{column_name}"
if is_indexed:
op.drop_index(f"ix_{table_name}_{column_name}", table_name=table_name)

op.add_column(table_name, sa.Column(temp_col_name, sa.String(), nullable=True))

stmt = sa.select(cur_table.c[column_name], cur_table.c[primary_key_name])
results = bind.execute(stmt).fetchall()
for row in results:
datetime_value = row[column_name]
primary_key_value = row[primary_key_name]
unix_time = _datetime_to_unix_value(datetime_value)
update_stmt = (
cur_table.update()
.where(cur_table.c[primary_key_name] == primary_key_value)
.values({temp_col_name: unix_time})
)
bind.execute(update_stmt)

with op.batch_alter_table(table_name) as batch_op:
batch_op.drop_column(column_name)
batch_op.alter_column(
temp_col_name,
new_column_name=column_name,
nullable=False,
)

if is_indexed:
op.create_index(f"ix_{table_name}_{column_name}", table_name, [column_name])


def upgrade() -> None:
for table_name, table_data in TABLE_DEFINITIONS.items():
unix_to_datetime(
table_name, table_data["primary_key_name"], table_data["column_names"]
)


def downgrade() -> None:
for table_name, table_data in TABLE_DEFINITIONS.items():
datetime_to_unix(
table_name, table_data["primary_key_name"], table_data["column_names"]
)
44 changes: 42 additions & 2 deletions mapcat/database/atomic_coadd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
Atomic map coadds
"""

from datetime import datetime
from typing import TYPE_CHECKING

from astropy.time import Time
from astropydantic import AstroPydanticTime
from sqlmodel import Field, Relationship, SQLModel

if TYPE_CHECKING:
Expand All @@ -12,6 +15,21 @@
from .links import AtomicMapToCoaddTable, CoaddMapToCoaddTable # pragma: no cover


class AtomicMapCoadd(SQLModel):
coadd_id: int

coadd_name: str
prefix_path: str

platform: str
interval: str
start_time: AstroPydanticTime
stop_time: AstroPydanticTime
freq_channel: str
geom_file_path: str
split_label: str


class AtomicMapCoaddTable(SQLModel, table=True):
__tablename__ = "atomic_map_coadds"

Expand All @@ -22,8 +40,8 @@ class AtomicMapCoaddTable(SQLModel, table=True):

platform: str = Field()
interval: str = Field()
start_time: float = Field()
stop_time: float = Field()
start_time: datetime = Field(nullable=False)
stop_time: datetime = Field(nullable=False)
freq_channel: str = Field()
geom_file_path: str = Field()
split_label: str = Field()
Expand All @@ -50,3 +68,25 @@ class AtomicMapCoaddTable(SQLModel, table=True):
"secondaryjoin": "AtomicMapCoaddTable.coadd_id == CoaddMapToCoaddTable.parent_coadd_id",
},
)

def to_model(self) -> AtomicMapCoadd:
"""
Return an AtomicMapCoadd model from this table entry.

Returns
-------
AtomicMapCoadd : AtomicMapCoadd
The AtomicMapCoadd model corresponding to this table entry.
"""
return AtomicMapCoadd(
coadd_id=self.coadd_id,
coadd_name=self.coadd_name,
prefix_path=self.prefix_path,
platform=self.platform,
interval=self.interval,
start_time=Time(self.start_time),
stop_time=Time(self.stop_time),
freq_channel=self.freq_channel,
geom_file_path=self.geom_file_path,
split_label=self.split_label,
)
94 changes: 93 additions & 1 deletion mapcat/database/atomic_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
Table for atomic maps.
"""

from datetime import datetime
from typing import TYPE_CHECKING

from astropy.time import Time
from astropydantic import AstroPydanticTime
from sqlmodel import Field, Relationship, SQLModel

from .links import AtomicMapToCoaddTable
Expand All @@ -12,6 +15,48 @@
from .atomic_coadd import AtomicMapCoaddTable # pragma: no cover


class AtomicMap(SQLModel):
atomic_map_id: int

obs_id: str
telescope: str
freq_channel: str
wafer: str
ctime: AstroPydanticTime
split_label: str

map_path: str | None
ivar_path: str | None

valid: bool | None
split_detail: str | None
prefix_path: str | None
azimuth: float | None
pwv: float | None
dpwv: float | None
total_weight_qu: float | None
mean_weight_qu: float | None
median_weight_qu: float | None
leakage_avg: float | None
noise_avg: float | None
ampl_2f_avg: float | None
gain_avg: float | None
f_hwp: float | None
roll_angle: float | None
scan_speed: float | None
scan_acc: float | None
sun_distance: float | None
ambient_temperature: float | None
uv: float | None
ra_center: float | None
dec_center: float | None
number_dets: int | None
moon_distance: float | None
wind_speed: float | None
wind_direction: float | None
rqu_avg: float | None


class AtomicMapTable(SQLModel, table=True):
__tablename__ = "atomic_maps"

Expand All @@ -21,7 +66,7 @@ class AtomicMapTable(SQLModel, table=True):
telescope: str = Field()
freq_channel: str = Field()
wafer: str = Field()
ctime: int = Field()
ctime: datetime = Field(nullable=False)
split_label: str = Field()

map_path: str | None = Field()
Expand Down Expand Up @@ -61,3 +106,50 @@ class AtomicMapTable(SQLModel, table=True):
back_populates="atomic_maps",
link_model=AtomicMapToCoaddTable,
)

def to_model(self) -> AtomicMap:
"""
Return an AtomicMap model from this table entry.

Returns
-------
AtomicMap : AtomicMap
The AtmoicMap model corresponding to this table entry.
"""
return AtomicMap(
atomic_map_id=self.atomic_map_id,
obs_id=self.obs_id,
telescope=self.telescope,
freq_channel=self.freq_channel,
wafer=self.wafer,
ctime=Time(self.ctime),
split_label=self.split_label,
map_path=self.map_path,
ivar_path=self.ivar_path,
valid=self.valid,
split_detail=self.split_detail,
prefix_path=self.prefix_path,
azimuth=self.azimuth,
pwv=self.pwv,
dpwv=self.dpwv,
total_weight_qu=self.total_weight_qu,
mean_weight_qu=self.mean_weight_qu,
leakage_avg=self.leakage_avg,
noise_avg=self.noise_avg,
ampl_2f_avg=self.ampl_2f_ave,
gain_avg=self.gain_avg,
f_hwp=self.f_hwp,
roll_angle=self.roll_angle,
scan_speed=self.scan_speed,
scan_acc=self.scan_acc,
sun_distance=self.sun_distance,
ambient_temperature=self.ambient_temperature,
uv=self.uv,
ra_center=self.ra_center,
dec_center=self.dec_center,
number_dets=self.number_dets,
moon_distance=self.moon_distance,
wind_speed=self.wind_speed,
wind_direction=self.wind_direction,
rqu_avg=self.rqu_avg,
)
Loading
Loading