Skip to content
1 change: 1 addition & 0 deletions db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from db.asset import *
from db.collabnet import *
from db.contact import *
from db.deployment import *
from db.geochronology import *
from db.geothermal import *
from db.field import *
Expand Down
47 changes: 47 additions & 0 deletions db/deployment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
This table is an installation log that creates a many-to-many relationship
between Things and Sensors, tracking which piece of hardware was installed
at which Thing and for what period of time.
"""

from typing import TYPE_CHECKING

from sqlalchemy import Integer, ForeignKey, Date, Numeric, Text
from sqlalchemy.orm import relationship, Mapped, mapped_column

from db.base import Base, AutoBaseMixin, ReleaseMixin, lexicon_term

if TYPE_CHECKING:
from db.thing import Thing
from db.sensor import Sensor


class Deployment(Base, AutoBaseMixin, ReleaseMixin):
"""
Represents the installation of a specific piece of equipment (Sensor) at a Thing
for a defined period. This is an Association Object.
"""

# --- Foreign Keys ---
thing_id: Mapped[int] = mapped_column(
Integer, ForeignKey("thing.id"), nullable=False
)
sensor_id: Mapped[int] = mapped_column(
Integer, ForeignKey("sensor.id"), nullable=False
)

# --- Columns ---
installation_date: Mapped[Date] = mapped_column(Date, nullable=False)
removal_date: Mapped[Date] = mapped_column(Date, nullable=True)
recording_interval: Mapped[int] = mapped_column(Integer, nullable=True)
recording_interval_units: Mapped[str] = lexicon_term(nullable=True)
hanging_cable_length: Mapped[float] = mapped_column(Numeric, nullable=True)
hanging_point_height: Mapped[float] = mapped_column(Numeric, nullable=True)
hanging_point_description: Mapped[str] = mapped_column(Text, nullable=True)
notes: Mapped[str] = mapped_column(Text, nullable=True)

# --- Relationships ---
# Many-To-One: A Deployment is for one Thing.
thing: Mapped["Thing"] = relationship("Thing", back_populates="deployments")
# Many-To-One: A Deployment is of one piece of equipment (sensor).
sensor: Mapped["Sensor"] = relationship("Sensor", back_populates="deployments")
Comment thread
ksmuczynski marked this conversation as resolved.
21 changes: 20 additions & 1 deletion db/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@
from datetime import datetime

from sqlalchemy import String, Integer, DateTime
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from sqlalchemy.orm import relationship, mapped_column, Mapped

from db.base import Base, AutoBaseMixin, ReleaseMixin

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
from db.deployment import Deployment
from db.thing import Thing
from db.observation import Observation


class Sensor(Base, AutoBaseMixin, ReleaseMixin):
"""
Expand All @@ -40,10 +48,21 @@ class Sensor(Base, AutoBaseMixin, ReleaseMixin):
recording_interval: Mapped[int] = mapped_column(Integer, nullable=True)
notes: Mapped[str] = mapped_column(String(50), nullable=True)

observations: Mapped[list["Observation"]] = relationship( # noqa: F821
# --- Relationships ---
# One-To-Many: A piece of Equipment can generate many Observations.
observations: Mapped[List["Observation"]] = relationship( # noqa: F821
"Observation",
back_populates="sensor",
)

# One-To-Many: A Sensor (or piece of equipment) can have many Deployments over its lifetime.
deployments: Mapped[List["Deployment"]] = relationship(
"Deployment", back_populates="sensor"
)

# --- Association Proxies ---
# Proxy to directly access the Things where this Equipment has been deployed.
things: AssociationProxy[List["Thing"]] = association_proxy("deployments", "thing")


# ============= EOF =============================================
24 changes: 23 additions & 1 deletion db/thing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
from db.asset import Asset
from db.base import AutoBaseMixin, Base, ReleaseMixin

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
from db.location import Location
from db.field import FieldEvent
from db.deployment import Deployment
from db.sensor import Sensor
from db.contact import Contact


class Thing(Base, AutoBaseMixin, ReleaseMixin):

Expand Down Expand Up @@ -87,10 +96,23 @@ class Thing(Base, AutoBaseMixin, ReleaseMixin):
)
)

field_events = relationship(
# --- Relationships ---
# One-To-Many: A Thing can have many FieldEvents over time.
field_events: Mapped[List["FieldEvent"]] = relationship(
"FieldEvent", back_populates="thing", cascade="all, delete-orphan", uselist=True
)

# One-To-Many: A Thing can have many Deployments of sensors (equipment) over time.
deployments: Mapped[List["Deployment"]] = relationship(
"Deployment", back_populates="thing", cascade="all, delete-orphan"
)

# --- Association Proxies ---
# Proxy to directly access the Sensor deployed at this Thing.
sensors: AssociationProxy[List["Sensor"]] = association_proxy(
"deployments", "sensor"
)


class ThingIdLink(Base, AutoBaseMixin, ReleaseMixin):
"""
Expand Down
Loading